Commit 1ce9420a8d77a68595ce319ac80ef7fd5f96d2c0

Authored by alwayssuper
2 parents f68f0de0 24a660b5

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

Showing 97 changed files with 1648 additions and 1787 deletions
plugins/enabled.txt
  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-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java
... ... @@ -66,7 +66,9 @@ public class CrmReceivableDO extends BaseDO {
66 66 */
67 67 private LocalDateTime returnTime;
68 68 /**
69   - * 回款方式,关联枚举{@link CrmReceivableReturnTypeEnum}
  69 + * 回款方式
  70 + *
  71 + * 枚举 {@link CrmReceivableReturnTypeEnum}
70 72 */
71 73 private Integer returnType;
72 74 /**
... ...
yudao-module-iot/yudao-module-iot-api/pom.xml
... ... @@ -22,6 +22,7 @@
22 22 <artifactId>yudao-common</artifactId>
23 23 </dependency>
24 24 <!-- PF4J -->
  25 + <!-- TODO 芋艿:这个依赖,要不要放在 api 包 -->
25 26 <dependency>
26 27 <groupId>org.pf4j</groupId>
27 28 <artifactId>pf4j-spring</artifactId>
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java
... ... @@ -7,6 +7,7 @@ import java.util.Map;
7 7 * 服务注册表 - 插架模块使用,无法使用 Spring 注入
8 8 */
9 9 public class ServiceRegistry {
  10 +
10 11 private static final Map<Class<?>, Object> services = new HashMap<>();
11 12  
12 13 /**
... ... @@ -31,4 +32,5 @@ public class ServiceRegistry {
31 32 public static <T> T getService(Class<T> serviceClass) {
32 33 return (T) services.get(serviceClass);
33 34 }
  35 +
34 36 }
35 37 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/DeviceDataApi.java renamed to 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;
  1 +package cn.iocoder.yudao.module.iot.api.device;
2 2  
3 3 /**
4 4 * 设备数据 API
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
... ... @@ -14,7 +14,6 @@ public interface ErrorCodeConstants {
14 14 ErrorCode PRODUCT_KEY_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在");
15 15 ErrorCode PRODUCT_STATUS_NOT_DELETE = new ErrorCode(1_050_001_002, "产品状是发布状态,不允许删除");
16 16 ErrorCode PRODUCT_STATUS_NOT_ALLOW_THING_MODEL = new ErrorCode(1_050_001_003, "产品状是发布状态,不允许操作物模型");
17   - ErrorCode PRODUCT_DEVICE_NOT_EXISTS = new ErrorCode(1_050_001_004, "产品设备类型不存在");
18 17  
19 18 // ========== 产品物模型 1-050-002-000 ============
20 19 ErrorCode THING_MODEL_NOT_EXISTS = new ErrorCode(1_050_002_000, "产品物模型不存在");
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java
... ... @@ -34,12 +34,10 @@ public enum IotPluginDeployTypeEnum implements IntArrayValuable {
34 34 }
35 35  
36 36 public static IotPluginDeployTypeEnum fromDeployType(Integer deployType) {
37   - for (IotPluginDeployTypeEnum value : values()) {
38   - if (value.getDeployType().equals(deployType)) {
39   - return value;
40   - }
41   - }
42   - return null;
  37 + return Arrays.stream(values())
  38 + .filter(value -> value.getDeployType().equals(deployType))
  39 + .findFirst()
  40 + .orElse(null);
43 41 }
44 42  
45 43 public static boolean isValidDeployType(Integer deployType) {
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java
... ... @@ -34,12 +34,10 @@ public enum IotPluginStatusEnum implements IntArrayValuable {
34 34 }
35 35  
36 36 public static IotPluginStatusEnum fromState(Integer state) {
37   - for (IotPluginStatusEnum value : values()) {
38   - if (value.getStatus().equals(state)) {
39   - return value;
40   - }
41   - }
42   - return null;
  37 + return Arrays.stream(values())
  38 + .filter(value -> value.getStatus().equals(state))
  39 + .findFirst()
  40 + .orElse(null);
43 41 }
44 42  
45 43 @Override
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java
... ... @@ -35,14 +35,11 @@ public enum IotPluginTypeEnum implements IntArrayValuable {
35 35 return ARRAYS;
36 36 }
37 37  
38   - // TODO @haohao:可以使用 hutool 简化
39 38 public static IotPluginTypeEnum fromType(Integer type) {
40   - for (IotPluginTypeEnum value : values()) {
41   - if (value.getType().equals(type)) {
42   - return value;
43   - }
44   - }
45   - return null;
  39 + return Arrays.stream(values())
  40 + .filter(value -> value.getType().equals(type))
  41 + .findFirst()
  42 + .orElse(null);
46 43 }
47 44  
48 45 public static boolean isValidType(Integer type) {
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotDataSpecsDataTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.enums.thingmodel;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * IoT 数据定义的数据类型枚举类
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@AllArgsConstructor
  12 +@Getter
  13 +public enum IotDataSpecsDataTypeEnum {
  14 +
  15 + INT("int"),
  16 + FLOAT("float"),
  17 + DOUBLE("double"),
  18 + ENUM("enum"),
  19 + BOOL("bool"),
  20 + TEXT("text"),
  21 + DATE("date"),
  22 + STRUCT("struct"),
  23 + ARRAY("array");
  24 +
  25 + private final String dataType;
  26 +
  27 +}
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotProductThingModelAccessModeEnum.java renamed to yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelAccessModeEnum.java
... ... @@ -4,13 +4,13 @@ import lombok.AllArgsConstructor;
4 4 import lombok.Getter;
5 5  
6 6 /**
7   - * IOT 访问方式枚举类
  7 + * IOT 产品物模型属性读取类型枚举
8 8 *
9 9 * @author ahh
10 10 */
11 11 @AllArgsConstructor
12 12 @Getter
13   -public enum IotProductThingModelAccessModeEnum {
  13 +public enum IotThingModelAccessModeEnum {
14 14  
15 15 READ_ONLY("r"),
16 16 READ_WRITE("rw");
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelParamDirectionEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.enums.thingmodel;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * IOT 产品物模型参数是输入参数还是输出参数枚举
  8 + *
  9 + * @author HUIHUI
  10 + */
  11 +@AllArgsConstructor
  12 +@Getter
  13 +public enum IotThingModelParamDirectionEnum {
  14 +
  15 + INPUT("input"), // 输入参数
  16 + OUTPUT("output"); // 输出参数
  17 +
  18 + private final String direction;
  19 +
  20 +}
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelServiceCallTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.enums.thingmodel;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * IOT 产品物模型服务调用方式枚举
  8 + *
  9 + * @author HUIHUI
  10 + */
  11 +@AllArgsConstructor
  12 +@Getter
  13 +public enum IotThingModelServiceCallTypeEnum {
  14 +
  15 + ASYNC("async"), // 异步调用
  16 + SYNC("sync"); // 同步调用
  17 +
  18 + private final String type;
  19 +
  20 +}
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelServiceEventTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.enums.thingmodel;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * IOT 产品物模型事件类型枚举
  8 + *
  9 + * @author HUIHUI
  10 + */
  11 +@AllArgsConstructor
  12 +@Getter
  13 +public enum IotThingModelServiceEventTypeEnum {
  14 +
  15 + INFO("info"), // 信息
  16 + ALERT("alert"), // 告警
  17 + ERROR("error"); // 故障
  18 +
  19 + private final String type;
  20 +
  21 +}
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotProductThingModelTypeEnum.java renamed to yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotThingModelTypeEnum.java
... ... @@ -13,13 +13,13 @@ import java.util.Arrays;
13 13 */
14 14 @AllArgsConstructor
15 15 @Getter
16   -public enum IotProductThingModelTypeEnum implements IntArrayValuable {
  16 +public enum IotThingModelTypeEnum implements IntArrayValuable {
17 17  
18 18 PROPERTY(1, "属性"),
19 19 SERVICE(2, "服务"),
20 20 EVENT(3, "事件");
21 21  
22   - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotProductThingModelTypeEnum::getType).toArray();
  22 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotThingModelTypeEnum::getType).toArray();
23 23  
24 24 /**
25 25 * 类型
... ... @@ -30,8 +30,8 @@ public enum IotProductThingModelTypeEnum implements IntArrayValuable {
30 30 */
31 31 private final String description;
32 32  
33   - public static IotProductThingModelTypeEnum valueOfType(Integer type) {
34   - for (IotProductThingModelTypeEnum value : values()) {
  33 + public static IotThingModelTypeEnum valueOfType(Integer type) {
  34 + for (IotThingModelTypeEnum value : values()) {
35 35 if (value.getType().equals(type)) {
36 36 return value;
37 37 }
... ...
yudao-module-iot/yudao-module-iot-biz/pom.xml
... ... @@ -27,13 +27,6 @@
27 27  
28 28 <dependency>
29 29 <groupId>cn.iocoder.boot</groupId>
30   - <artifactId>yudao-module-iot-plugin-api</artifactId>
31   - <version>0.0.1</version>
32   - <scope>compile</scope>
33   - </dependency>
34   -
35   - <dependency>
36   - <groupId>cn.iocoder.boot</groupId>
37 30 <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
38 31 </dependency>
39 32  
... ... @@ -81,6 +74,7 @@
81 74 <dependency>
82 75 <groupId>org.pf4j</groupId>
83 76 <artifactId>pf4j-spring</artifactId>
  77 + <!-- TODO @芋艿:可以放到 bom 里配置 -->
84 78 <exclusions>
85 79 <exclusion>
86 80 <groupId>org.slf4j</groupId>
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java
1 1 package cn.iocoder.yudao.module.iot.api.device;
2 2  
3   -import cn.iocoder.yudao.module.iot.api.DeviceDataApi;
4   -import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
  3 +import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
5 4 import org.springframework.stereotype.Service;
6 5 import org.springframework.validation.annotation.Validated;
7 6  
... ... @@ -15,11 +14,11 @@ import javax.annotation.Resource;
15 14 public class DeviceDataApiImpl implements DeviceDataApi {
16 15  
17 16 @Resource
18   - private IotDeviceDataService iotDeviceDataService;
  17 + private IotDevicePropertyDataService deviceDataService;
19 18  
20 19 @Override
21 20 public void saveDeviceData(String productKey, String deviceName, String message) {
22   - iotDeviceDataService.saveDeviceData(productKey, deviceName, message);
  21 + deviceDataService.saveDeviceData(productKey, deviceName, message);
23 22 }
24 23  
25 24 }
26 25 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java
  1 +/**
  2 + * 占位
  3 + *
  4 + * TODO 芋艿:后续删除
  5 + */
1 6 package cn.iocoder.yudao.module.iot.api;
2 7 \ 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
... ... @@ -7,7 +7,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDevi
7 7 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataRespVO;
8 8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
9 9 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotTimeDataRespVO;
10   -import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
  10 +import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
11 11 import io.swagger.v3.oas.annotations.Operation;
12 12 import io.swagger.v3.oas.annotations.tags.Tag;
13 13 import jakarta.annotation.Resource;
... ... @@ -29,7 +29,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
29 29 public class IotDeviceDataController {
30 30  
31 31 @Resource
32   - private IotDeviceDataService deviceDataService;
  32 + private IotDevicePropertyDataService deviceDataService;
33 33  
34 34 // TODO @浩浩:这里的 /latest-list,包括方法名。
35 35 @GetMapping("/latest")
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginInfoController.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/PluginInfoController.java
1   -package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
  1 +package cn.iocoder.yudao.module.iot.controller.admin.plugin;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
7   -import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoRespVO;
8   -import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoImportReqVO;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO;
  8 +import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoRespVO;
  9 +import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO;
9 10 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
10   -import cn.iocoder.yudao.module.iot.service.plugininfo.PluginInfoService;
  11 +import cn.iocoder.yudao.module.iot.service.plugin.PluginInfoService;
11 12 import io.swagger.v3.oas.annotations.Operation;
12 13 import io.swagger.v3.oas.annotations.Parameter;
13 14 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -16,11 +17,8 @@ import jakarta.validation.Valid;
16 17 import org.springframework.security.access.prepost.PreAuthorize;
17 18 import org.springframework.validation.annotation.Validated;
18 19 import org.springframework.web.bind.annotation.*;
19   -import org.springframework.web.multipart.MultipartFile;
20 20  
21   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
22 21 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
23   -import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;
24 22  
25 23 @Tag(name = "管理后台 - IoT 插件信息")
26 24 @RestController
... ... @@ -72,16 +70,11 @@ public class PluginInfoController {
72 70 return success(BeanUtils.toBean(pageResult, PluginInfoRespVO.class));
73 71 }
74 72  
75   - @RequestMapping(value = "/update-jar",
76   - method = {RequestMethod.POST, RequestMethod.PUT}) // 解决 uni-app 不支持 Put 上传文件的问题
77   - @Operation(summary = "上传Jar包")
78   - public CommonResult<Boolean> uploadJar(
79   - @RequestParam("id") Long id,
80   - @RequestParam("jar") MultipartFile file) throws Exception {
81   - if (file.isEmpty()) {
82   - throw exception(FILE_IS_EMPTY);
83   - }
84   - pluginInfoService.uploadJar(id, file);
  73 + @PostMapping("/upload-file")
  74 + @Operation(summary = "上传插件文件")
  75 + @PreAuthorize("@ss.hasPermission('iot:plugin-info:update')")
  76 + public CommonResult<Boolean> uploadFile(@Valid PluginInfoImportReqVO reqVO) {
  77 + pluginInfoService.uploadFile(reqVO.getId(), reqVO.getFile());
85 78 return success(true);
86 79 }
87 80  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoImportReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +import org.springframework.web.multipart.MultipartFile;
  7 +
  8 +@Schema(description = "管理后台 - IoT 插件上传 Request VO")
  9 +@Data
  10 +public class PluginInfoImportReqVO {
  11 +
  12 + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
  13 + private Long id;
  14 +
  15 + @Schema(description = "插件文件", requiredMode = Schema.RequiredMode.REQUIRED)
  16 + @NotNull(message = "插件文件不能为空")
  17 + private MultipartFile file;
  18 +
  19 +}
0 20 \ 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/PluginInfoPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - IoT 插件信息分页 Request VO")
  8 +@Data
  9 +public class PluginInfoPageReqVO extends PageParam {
  10 +
  11 + @Schema(description = "插件名称", example = "http")
  12 + private String name;
  13 +
  14 + @Schema(description = "状态")
  15 + private Integer status;
  16 +
  17 +}
0 18 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoRespVO.java renamed to 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.plugininfo.vo;
  1 +package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
2 2  
3 3 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4 4 import com.alibaba.excel.annotation.ExcelProperty;
... ... @@ -16,9 +16,9 @@ public class PluginInfoRespVO {
16 16 @ExcelProperty("主键 ID")
17 17 private Long id;
18 18  
19   - @Schema(description = "插件包 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
20   - @ExcelProperty("插件包 ID")
21   - private String pluginId;
  19 + @Schema(description = "插件包标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
  20 + @ExcelProperty("插件包标识符")
  21 + private String pluginKey;
22 22  
23 23 @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
24 24 @ExcelProperty("插件名称")
... ... @@ -34,7 +34,7 @@ public class PluginInfoRespVO {
34 34  
35 35 @Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED)
36 36 @ExcelProperty("插件包文件名")
37   - private String file;
  37 + private String fileName;
38 38  
39 39 @Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED)
40 40 @ExcelProperty("插件版本")
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoSaveReqVO.java renamed to 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.plugininfo.vo;
  1 +package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.*;
... ... @@ -10,8 +10,8 @@ public class PluginInfoSaveReqVO {
10 10 @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
11 11 private Long id;
12 12  
13   - @Schema(description = "插件包id", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
14   - private String pluginId;
  13 + @Schema(description = "插件包标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
  14 + private String pluginKey;
15 15  
16 16 @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
17 17 private String name;
... ... @@ -23,7 +23,7 @@ public class PluginInfoSaveReqVO {
23 23 private Integer deployType;
24 24  
25 25 @Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED)
26   - private String file;
  26 + private String fileName;
27 27  
28 28 @Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED)
29 29 private String version;
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstancePageReqVO.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInstancePageReqVO.java
1   -package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
  1 +package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
2 2  
3 3 import lombok.*;
4 4 import io.swagger.v3.oas.annotations.media.Schema;
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceRespVO.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInstanceRespVO.java
1   -package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
  1 +package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.*;
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceSaveReqVO.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInstanceSaveReqVO.java
1   -package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
  1 +package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.*;
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginController.java deleted
1   -package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
2   -
3   -import jakarta.annotation.Resource;
4   -import org.pf4j.spring.SpringPluginManager;
5   -import org.springframework.beans.factory.annotation.Value;
6   -import org.springframework.http.HttpStatus;
7   -import org.springframework.http.ResponseEntity;
8   -import org.springframework.web.bind.annotation.*;
9   -import org.springframework.web.multipart.MultipartFile;
10   -
11   -import javax.annotation.security.PermitAll;
12   -import java.io.IOException;
13   -import java.nio.file.Files;
14   -import java.nio.file.Path;
15   -import java.nio.file.Paths;
16   -import java.nio.file.StandardCopyOption;
17   -import java.util.List;
18   -import java.util.stream.Collectors;
19   -
20   -/**
21   - * 插件 Controller 测试用例
22   - */
23   -@RestController
24   -@RequestMapping("/iot/plugins")
25   -public class PluginController {
26   -
27   - @Resource
28   - private SpringPluginManager springPluginManager;
29   -
30   - @Value("${pf4j.pluginsDir}")
31   - private String pluginsDir;
32   -
33   - /**
34   - * 上传插件 JAR 文件并加载插件
35   - *
36   - * @param file 上传的 JAR 文件
37   - * @return 上传结果
38   - */
39   - @PermitAll
40   - @PostMapping("/upload")
41   - public ResponseEntity<String> uploadPlugin(@RequestParam("file") MultipartFile file) {
42   - if (file.isEmpty()) {
43   - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("上传的文件为空");
44   - }
45   -
46   - // 确保插件目录存在
47   - Path pluginsPath = Paths.get(pluginsDir);
48   - try {
49   - if (!Files.exists(pluginsPath)) {
50   - Files.createDirectories(pluginsPath);
51   - }
52   -
53   - // 保存上传的 JAR 文件到插件目录
54   - String filename = file.getOriginalFilename();
55   - if (filename == null || (!filename.endsWith(".jar") && !filename.endsWith(".zip"))) {
56   - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("上传的文件不是 JAR 或 ZIP 文件");
57   - }
58   -
59   - Path jarPath = pluginsPath.resolve(filename);
60   -
61   - Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING);
62   -
63   - // 加载插件
64   - String pluginId = springPluginManager.loadPlugin(jarPath.toAbsolutePath());
65   -
66   - // 启动插件
67   - springPluginManager.startPlugin(pluginId);
68   -
69   - return ResponseEntity.ok("插件上传并加载成功");
70   - } catch (IOException e) {
71   - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("上传插件时发生错误: " + e.getMessage());
72   - } catch (Exception e) {
73   - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("加载插件时发生错误: " + e.getMessage());
74   - }
75   - }
76   -
77   - /**
78   - * 卸载指定插件
79   - *
80   - * @param pluginId 插件 ID
81   - * @return 卸载结果
82   - */
83   - @PermitAll
84   - @DeleteMapping("/unload/{pluginId}")
85   - public ResponseEntity<String> unloadPlugin(@PathVariable String pluginId) {
86   - if (springPluginManager.getPlugins().stream().noneMatch(plugin -> plugin.getDescriptor().getPluginId().equals(pluginId))) {
87   - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("插件未加载: " + pluginId);
88   - }
89   -
90   - springPluginManager.stopPlugin(pluginId);
91   - springPluginManager.unloadPlugin(pluginId);
92   -
93   - // 删除插件 JAR 文件(可选)
94   -// PluginWrapper plugin = pluginManager.getPlugin(pluginId);
95   -// PluginDescriptor descriptor = plugin.getDescriptor();
96   -// Path jarPath = Paths.get(pluginsDir).resolve(descriptor.getPluginId() + ".jar");
97   -// Files.deleteIfExists(jarPath);
98   -
99   - return ResponseEntity.ok("插件卸载成功: " + pluginId);
100   - }
101   -
102   - /**
103   - * 列出所有已加载的插件
104   - *
105   - * @return 插件列表
106   - */
107   - @PermitAll
108   - @GetMapping("/list")
109   - public ResponseEntity<List<String>> listPlugins() {
110   - List<String> plugins = springPluginManager.getPlugins().stream()
111   - .map(plugin -> plugin.getDescriptor().getPluginId())
112   - .collect(Collectors.toList());
113   - return ResponseEntity.ok(plugins);
114   - }
115   -
116   -}
117 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoPageReqVO.java deleted
1   -package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo;
2   -
3   -import cn.iocoder.yudao.framework.common.validation.InEnum;
4   -import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginTypeEnum;
5   -import lombok.*;
6   -import io.swagger.v3.oas.annotations.media.Schema;
7   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
8   -import org.springframework.format.annotation.DateTimeFormat;
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   -// TODO @haohao:只查询必要字段哈
14   -@Schema(description = "管理后台 - IoT 插件信息分页 Request VO")
15   -@Data
16   -public class PluginInfoPageReqVO extends PageParam {
17   -
18   - @Schema(description = "插件包 ID ", example = "24627")
19   - private String pluginId;
20   -
21   - @Schema(description = "插件名称", example = "赵六")
22   - private String name;
23   -
24   - @Schema(description = "描述", example = "你猜")
25   - private String description;
26   -
27   - @Schema(description = "部署方式", example = "2")
28   - private Integer deployType;
29   -
30   - @Schema(description = "插件包文件名")
31   - private String file;
32   -
33   - @Schema(description = "插件版本")
34   - private String version;
35   -
36   - @Schema(description = "插件类型", example = "2")
37   - @InEnum(IotPluginTypeEnum.class)
38   - private Integer type;
39   -
40   - @Schema(description = "设备插件协议类型")
41   - private String protocol;
42   -
43   - @Schema(description = "状态")
44   - private Integer status;
45   -
46   - @Schema(description = "插件配置项描述信息")
47   - private String configSchema;
48   -
49   - @Schema(description = "插件配置信息")
50   - private String config;
51   -
52   - @Schema(description = "插件脚本")
53   - private String script;
54   -
55   - @Schema(description = "创建时间")
56   - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
57   - private LocalDateTime[] createTime;
58   -
59   -}
60 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/PluginInstanceController.java deleted
1   -package cn.iocoder.yudao.module.iot.controller.admin.plugininstance;
2   -
3   -import org.springframework.web.bind.annotation.*;
4   -import jakarta.annotation.Resource;
5   -import org.springframework.validation.annotation.Validated;
6   -import org.springframework.security.access.prepost.PreAuthorize;
7   -import io.swagger.v3.oas.annotations.tags.Tag;
8   -import io.swagger.v3.oas.annotations.Parameter;
9   -import io.swagger.v3.oas.annotations.Operation;
10   -
11   -import jakarta.validation.*;
12   -import jakarta.servlet.http.*;
13   -import java.util.*;
14   -import java.io.IOException;
15   -
16   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
17   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
18   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
19   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
20   -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
21   -
22   -import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
23   -
24   -import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
25   -import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
26   -
27   -import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*;
28   -import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
29   -import cn.iocoder.yudao.module.iot.service.plugininstance.PluginInstanceService;
30   -
31   -@Tag(name = "管理后台 - IoT 插件实例")
32   -@RestController
33   -@RequestMapping("/iot/plugin-instance")
34   -@Validated
35   -public class PluginInstanceController {
36   -
37   - @Resource
38   - private PluginInstanceService pluginInstanceService;
39   -
40   - @PostMapping("/create")
41   - @Operation(summary = "创建IoT 插件实例")
42   - @PreAuthorize("@ss.hasPermission('iot:plugin-instance:create')")
43   - public CommonResult<Long> createPluginInstance(@Valid @RequestBody PluginInstanceSaveReqVO createReqVO) {
44   - return success(pluginInstanceService.createPluginInstance(createReqVO));
45   - }
46   -
47   - @PutMapping("/update")
48   - @Operation(summary = "更新IoT 插件实例")
49   - @PreAuthorize("@ss.hasPermission('iot:plugin-instance:update')")
50   - public CommonResult<Boolean> updatePluginInstance(@Valid @RequestBody PluginInstanceSaveReqVO updateReqVO) {
51   - pluginInstanceService.updatePluginInstance(updateReqVO);
52   - return success(true);
53   - }
54   -
55   - @DeleteMapping("/delete")
56   - @Operation(summary = "删除IoT 插件实例")
57   - @Parameter(name = "id", description = "编号", required = true)
58   - @PreAuthorize("@ss.hasPermission('iot:plugin-instance:delete')")
59   - public CommonResult<Boolean> deletePluginInstance(@RequestParam("id") Long id) {
60   - pluginInstanceService.deletePluginInstance(id);
61   - return success(true);
62   - }
63   -
64   - @GetMapping("/get")
65   - @Operation(summary = "获得IoT 插件实例")
66   - @Parameter(name = "id", description = "编号", required = true, example = "1024")
67   - @PreAuthorize("@ss.hasPermission('iot:plugin-instance:query')")
68   - public CommonResult<PluginInstanceRespVO> getPluginInstance(@RequestParam("id") Long id) {
69   - PluginInstanceDO pluginInstance = pluginInstanceService.getPluginInstance(id);
70   - return success(BeanUtils.toBean(pluginInstance, PluginInstanceRespVO.class));
71   - }
72   -
73   - @GetMapping("/page")
74   - @Operation(summary = "获得IoT 插件实例分页")
75   - @PreAuthorize("@ss.hasPermission('iot:plugin-instance:query')")
76   - public CommonResult<PageResult<PluginInstanceRespVO>> getPluginInstancePage(@Valid PluginInstancePageReqVO pageReqVO) {
77   - PageResult<PluginInstanceDO> pageResult = pluginInstanceService.getPluginInstancePage(pageReqVO);
78   - return success(BeanUtils.toBean(pageResult, PluginInstanceRespVO.class));
79   - }
80   -
81   - @GetMapping("/export-excel")
82   - @Operation(summary = "导出IoT 插件实例 Excel")
83   - @PreAuthorize("@ss.hasPermission('iot:plugin-instance:export')")
84   - @ApiAccessLog(operateType = EXPORT)
85   - public void exportPluginInstanceExcel(@Valid PluginInstancePageReqVO pageReqVO,
86   - HttpServletResponse response) throws IOException {
87   - pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
88   - List<PluginInstanceDO> list = pluginInstanceService.getPluginInstancePage(pageReqVO).getList();
89   - // 导出 Excel
90   - ExcelUtils.write(response, "IoT 插件实例.xls", "数据", PluginInstanceRespVO.class,
91   - BeanUtils.toBean(list, PluginInstanceRespVO.class));
92   - }
93   -
94   -}
95 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotProductThingModelController.http renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotThingModelController.http
... ... @@ -25,8 +25,7 @@ Authorization: Bearer {{token}}
25 25 "defaultValue": "30",
26 26 "unit": "%",
27 27 "unitName": "百分比"
28   - },
29   - "description": "当前温度值"
  28 + }
30 29 }
31 30 }
32 31  
... ... @@ -46,7 +45,6 @@ Authorization: Bearer {{token}}
46 45 "property": {
47 46 "identifier": "switch",
48 47 "name": "开关",
49   - "description": "温度计开关",
50 48 "accessMode": "rw",
51 49 "required": true,
52 50 "dataType": "bool",
... ... @@ -81,7 +79,6 @@ Authorization: Bearer {{token}}
81 79 "property": {
82 80 "identifier": "argb",
83 81 "name": "温度计 argb 颜色",
84   - "description": "温度计 argb 颜色",
85 82 "accessMode": "rw",
86 83 "required": true,
87 84 "dataType": "array",
... ... @@ -93,7 +90,6 @@ Authorization: Bearer {{token}}
93 90 {
94 91 "identifier": "switch",
95 92 "name": "开关",
96   - "description": "温度计开关",
97 93 "accessMode": "rw",
98 94 "required": true,
99 95 "dataType": "struct",
... ... @@ -126,8 +122,7 @@ Authorization: Bearer {{token}}
126 122 "defaultValue": "30",
127 123 "unit": "%",
128 124 "unitName": "百分比"
129   - },
130   - "description": "当前温度值"
  125 + }
131 126 }
132 127 ]
133 128 }
... ... @@ -151,7 +146,6 @@ Authorization: Bearer {{token}}
151 146 "property": {
152 147 "identifier": "switch",
153 148 "name": "开关",
154   - "description": "温度计开关",
155 149 "accessMode": "r",
156 150 "required": true,
157 151 "dataType": "bool",
... ... @@ -176,7 +170,7 @@ tenant-id: {{adminTenentId}}
176 170 Authorization: Bearer {{token}}
177 171  
178 172 ### 请求 /iot/product-thing-model/get 接口 => 成功
179   -GET {{baseUrl}}/iot/product-thing-model/get?id=40
  173 +GET {{baseUrl}}/iot/product-thing-model/get?id=67
180 174 tenant-id: {{adminTenentId}}
181 175 Authorization: Bearer {{token}}
182 176  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotProductThingModelController.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/IotThingModelController.java
... ... @@ -3,12 +3,12 @@ package cn.iocoder.yudao.module.iot.controller.admin.thingmodel;
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelPageReqVO;
7   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelRespVO;
8   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelSaveReqVO;
9   -import cn.iocoder.yudao.module.iot.convert.thingmodel.IotProductThingModelConvert;
10   -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
11   -import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelPageReqVO;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelRespVO;
  8 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelSaveReqVO;
  9 +import cn.iocoder.yudao.module.iot.convert.thingmodel.IotThingModelConvert;
  10 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
  11 +import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
12 12 import io.swagger.v3.oas.annotations.Operation;
13 13 import io.swagger.v3.oas.annotations.Parameter;
14 14 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -24,61 +24,61 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
24 24  
25 25 @Tag(name = "管理后台 - IoT 产品物模型")
26 26 @RestController
27   -@RequestMapping("/iot/product-thing-model")
  27 +@RequestMapping("/iot/thing-model")
28 28 @Validated
29   -public class IotProductThingModelController {
  29 +public class IotThingModelController {
30 30  
31 31 @Resource
32   - private IotProductThingModelService thingModelService;
  32 + private IotThingModelService thingModelService;
33 33  
34 34 @PostMapping("/create")
35 35 @Operation(summary = "创建产品物模型")
36   - @PreAuthorize("@ss.hasPermission('iot:product-thing-model:create')")
37   - public CommonResult<Long> createProductThingModel(@Valid @RequestBody IotProductThingModelSaveReqVO createReqVO) {
38   - return success(thingModelService.createProductThingModel(createReqVO));
  36 + @PreAuthorize("@ss.hasPermission('iot:thing-model:create')")
  37 + public CommonResult<Long> createThingModel(@Valid @RequestBody IotThingModelSaveReqVO createReqVO) {
  38 + return success(thingModelService.createThingModel(createReqVO));
39 39 }
40 40  
41 41 @PutMapping("/update")
42 42 @Operation(summary = "更新产品物模型")
43   - @PreAuthorize("@ss.hasPermission('iot:product-thing-model:update')")
44   - public CommonResult<Boolean> updateProductThingModel(@Valid @RequestBody IotProductThingModelSaveReqVO updateReqVO) {
45   - thingModelService.updateProductThingModel(updateReqVO);
  43 + @PreAuthorize("@ss.hasPermission('iot:thing-model:update')")
  44 + public CommonResult<Boolean> updateThingModel(@Valid @RequestBody IotThingModelSaveReqVO updateReqVO) {
  45 + thingModelService.updateThingModel(updateReqVO);
46 46 return success(true);
47 47 }
48 48  
49 49 @DeleteMapping("/delete")
50 50 @Operation(summary = "删除产品物模型")
51 51 @Parameter(name = "id", description = "编号", required = true)
52   - @PreAuthorize("@ss.hasPermission('iot:product-thing-model:delete')")
53   - public CommonResult<Boolean> deleteProductThingModel(@RequestParam("id") Long id) {
54   - thingModelService.deleteProductThingModel(id);
  52 + @PreAuthorize("@ss.hasPermission('iot:thing-model:delete')")
  53 + public CommonResult<Boolean> deleteThingModel(@RequestParam("id") Long id) {
  54 + thingModelService.deleteThingModel(id);
55 55 return success(true);
56 56 }
57 57  
58 58 @GetMapping("/get")
59 59 @Operation(summary = "获得产品物模型")
60 60 @Parameter(name = "id", description = "编号", required = true)
61   - @PreAuthorize("@ss.hasPermission('iot:product-thing-model:query')")
62   - public CommonResult<IotProductThingModelRespVO> getProductThingModel(@RequestParam("id") Long id) {
63   - IotProductThingModelDO thingModel = thingModelService.getProductThingModel(id);
64   - return success(IotProductThingModelConvert.INSTANCE.convert(thingModel));
  61 + @PreAuthorize("@ss.hasPermission('iot:thing-model:query')")
  62 + public CommonResult<IotThingModelRespVO> getThingModel(@RequestParam("id") Long id) {
  63 + IotThingModelDO thingModel = thingModelService.getThingModel(id);
  64 + return success(IotThingModelConvert.INSTANCE.convert(thingModel));
65 65 }
66 66  
67 67 @GetMapping("/list-by-product-id")
68 68 @Operation(summary = "获得产品物模型")
69 69 @Parameter(name = "productId", description = "产品ID", required = true, example = "1024")
70   - @PreAuthorize("@ss.hasPermission('iot:product-thing-model:query')")
71   - public CommonResult<List<IotProductThingModelRespVO>> getProductThingModelListByProductId(@RequestParam("productId") Long productId) {
72   - List<IotProductThingModelDO> list = thingModelService.getProductThingModelListByProductId(productId);
73   - return success(IotProductThingModelConvert.INSTANCE.convertList(list));
  70 + @PreAuthorize("@ss.hasPermission('iot:thing-model:query')")
  71 + public CommonResult<List<IotThingModelRespVO>> getThingModelListByProductId(@RequestParam("productId") Long productId) {
  72 + List<IotThingModelDO> list = thingModelService.getThingModelListByProductId(productId);
  73 + return success(IotThingModelConvert.INSTANCE.convertList(list));
74 74 }
75 75  
76 76 @GetMapping("/page")
77 77 @Operation(summary = "获得产品物模型分页")
78   - @PreAuthorize("@ss.hasPermission('iot:product-thing-model:query')")
79   - public CommonResult<PageResult<IotProductThingModelRespVO>> getProductThingModelPage(@Valid IotProductThingModelPageReqVO pageReqVO) {
80   - PageResult<IotProductThingModelDO> pageResult = thingModelService.getProductThingModelPage(pageReqVO);
81   - return success(BeanUtils.toBean(pageResult, IotProductThingModelRespVO.class));
  78 + @PreAuthorize("@ss.hasPermission('iot:thing-model:query')")
  79 + public CommonResult<PageResult<IotThingModelRespVO>> getThingModelPage(@Valid IotThingModelPageReqVO pageReqVO) {
  80 + PageResult<IotThingModelDO> pageResult = thingModelService.getProductThingModelPage(pageReqVO);
  81 + return success(BeanUtils.toBean(pageResult, IotThingModelRespVO.class));
82 82 }
83 83  
84 84 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/ThingModelEvent.java
1 1 package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model;
2 2  
3   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelArgument;
  3 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelServiceEventTypeEnum;
4 4 import lombok.Data;
  5 +
5 6 import java.util.List;
6 7  
  8 +/**
  9 + * 物模型中的事件
  10 + *
  11 + * @author HUIHUI
  12 + */
7 13 @Data
8 14 public class ThingModelEvent {
9 15  
... ... @@ -16,17 +22,24 @@ public class ThingModelEvent {
16 22 */
17 23 private String name;
18 24 /**
19   - * 事件描述
  25 + * 是否是标准品类的必选事件
20 26 */
21   - private String description;
22   -
  27 + private Boolean required;
23 28 /**
24 29 * 事件类型
25 30 *
26   - * "info"、"alert"、"error"
  31 + * 枚举 {@link IotThingModelServiceEventTypeEnum}
27 32 */
28 33 private String type;
29   - private List<ThingModelArgument> outputData;
  34 + /**
  35 + * 事件的输出参数
  36 + *
  37 + * 输出参数定义事件调用后返回的结果或反馈信息,用于确认操作结果或提供额外的信息。
  38 + */
  39 + private List<ThingModelParam> outputParams;
  40 + /**
  41 + * 标识设备需要执行的具体操作
  42 + */
30 43 private String method;
31 44  
32 45 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/ThingModelParam.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model;
  2 +
  3 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDataSpecs;
  4 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelParamDirectionEnum;
  5 +import lombok.Data;
  6 +
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * IOT 产品物模型中的参数
  11 + *
  12 + * @author HUIHUI
  13 + */
  14 +@Data
  15 +public class ThingModelParam {
  16 +
  17 + /**
  18 + * 参数标识符
  19 + */
  20 + private String identifier;
  21 + /**
  22 + * 参数名称
  23 + */
  24 + private String name;
  25 + /**
  26 + * 用于区分输入或输出参数
  27 + *
  28 + * 枚举 {@link IotThingModelParamDirectionEnum}
  29 + */
  30 + private String direction;
  31 + /**
  32 + * 参数的序号。从 0 开始排序,且不能重复。
  33 + *
  34 + * TODO 考虑要不要序号,感觉是要的, 先留一手看看
  35 + */
  36 + private Integer paraOrder;
  37 + /**
  38 + * 参数值的数据类型,与 dataSpecs 的 dataType 保持一致
  39 + */
  40 + private String dataType;
  41 + /**
  42 + * 参数值的数据类型(dataType)为非列表型(int、float、double、text、date、array)的数据规范存储在 dataSpecs 中
  43 + */
  44 + private ThingModelDataSpecs dataSpecs;
  45 + /**
  46 + * 参数值的数据类型(dataType)为列表型(enum、bool、struct)的数据规范存储在 dataSpecsList 中
  47 + */
  48 + private List<ThingModelDataSpecs> dataSpecsList;
  49 +
  50 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/ThingModelProperty.java
1 1 package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model;
2 2  
3 3 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDataSpecs;
4   -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelAccessModeEnum;
  4 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelAccessModeEnum;
5 5 import lombok.Data;
6 6  
7 7 import java.util.List;
... ... @@ -25,12 +25,9 @@ public class ThingModelProperty {
25 25 */
26 26 private String name;
27 27 /**
28   - * 属性描述
29   - */
30   - private String description;
31   - /**
32 28 * 云端可以对该属性进行的操作类型
33   - * 关联枚举 {@link IotProductThingModelAccessModeEnum}
  29 + *
  30 + * 枚举 {@link IotThingModelAccessModeEnum}
34 31 */
35 32 private String accessMode;
36 33 /**
... ... @@ -42,6 +39,8 @@ public class ThingModelProperty {
42 39 private Boolean required;
43 40 /**
44 41 * 数据类型,与 dataSpecs 的 dataType 保持一致
  42 + *
  43 + * 枚举 {@link cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum}
45 44 */
46 45 private String dataType;
47 46 /**
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/ThingModelService.java
1 1 package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model;
2 2  
3   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelArgument;
  3 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelServiceCallTypeEnum;
4 4 import lombok.Data;
  5 +
5 6 import java.util.List;
6 7  
  8 +/**
  9 + * 物模型中的服务
  10 + *
  11 + * @author HUIHUI
  12 + */
7 13 @Data
8 14 public class ThingModelService {
9 15  
... ... @@ -16,18 +22,30 @@ public class ThingModelService {
16 22 */
17 23 private String name;
18 24 /**
19   - * 服务描述
  25 + * 是否是标准品类的必选服务
20 26 */
21   - private String description;
22   -
  27 + private Boolean required;
23 28 /**
24 29 * 调用类型
25 30 *
26   - * "sync"、"async"
  31 + * 枚举 {@link IotThingModelServiceCallTypeEnum}
27 32 */
28 33 private String callType;
29   - private List<ThingModelArgument> inputData;
30   - private List<ThingModelArgument> outputData;
  34 + /**
  35 + * 服务的输入参数
  36 + *
  37 + * 输入参数定义服务调用时所需提供的信息,用于控制设备行为或执行特定任务
  38 + */
  39 + private List<ThingModelParam> inputParams;
  40 + /**
  41 + * 服务的输出参数
  42 + *
  43 + * 输出参数定义服务调用后返回的结果或反馈信息,用于确认操作结果或提供额外的信息。
  44 + */
  45 + private List<ThingModelParam> outputParams;
  46 + /**
  47 + * 标识设备需要执行的具体操作
  48 + */
31 49 private String method;
32 50  
33 51 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/dataType/ThingModelArgument.java deleted
1   -package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType;
2   -
3   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
4   -import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
5   -import lombok.Data;
6   -
7   -@Data
8   -@JsonIgnoreProperties(ignoreUnknown = true)
9   -public class ThingModelArgument {
10   -
11   - public static final String DIRECTION_INPUT = "input";
12   - public static final String DIRECTION_OUTPUT = "output";
13   -
14   - private String identifier;
15   - private String name;
16   - /**
17   - * 物模型中的属性
18   - */
19   - private ThingModelProperty property;
20   - /**
21   - * 用于区分输入或输出参数,"input" 或 "output"
22   - */
23   - private String direction;
24   - private String description;
25   -
26   -}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/dataType/ThingModelDateOrTextDataSpecs.java
... ... @@ -20,7 +20,7 @@ public class ThingModelDateOrTextDataSpecs extends ThingModelDataSpecs {
20 20 * 数据长度,单位为字节。取值不能超过 2048。
21 21 * 当 dataType 为 text 时,需传入该参数。
22 22 */
23   - private Long length;
  23 + private Integer length;
24 24 /**
25 25 * 默认值,可选参数,用于存储默认值。
26 26 */
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/dataType/ThingModelStructDataSpecs.java
1 1 package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType;
2 2  
3   -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelAccessModeEnum;
  3 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelAccessModeEnum;
4 4 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
5 5 import lombok.Data;
6 6 import lombok.EqualsAndHashCode;
... ... @@ -26,19 +26,13 @@ public class ThingModelStructDataSpecs extends ThingModelDataSpecs {
26 26 */
27 27 private String name;
28 28 /**
29   - * 属性描述
30   - */
31   - private String description;
32   - /**
33 29 * 云端可以对该属性进行的操作类型
34   - * 关联枚举 {@link IotProductThingModelAccessModeEnum}
  30 + *
  31 + * 枚举 {@link IotThingModelAccessModeEnum}
35 32 */
36 33 private String accessMode;
37 34 /**
38   - * 是否是标准品类的必选服务。
39   - *
40   - * - true:是
41   - * - false:否
  35 + * 是否是标准品类的必选服务
42 36 */
43 37 private Boolean required;
44 38 /**
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotProductThingModelPageReqVO.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelPageReqVO.java
... ... @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
5   -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
  5 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
6 6 import io.swagger.v3.oas.annotations.media.Schema;
7 7 import jakarta.validation.constraints.NotNull;
8 8 import lombok.Data;
... ... @@ -13,7 +13,7 @@ import lombok.ToString;
13 13 @Data
14 14 @EqualsAndHashCode(callSuper = true)
15 15 @ToString(callSuper = true)
16   -public class IotProductThingModelPageReqVO extends PageParam {
  16 +public class IotThingModelPageReqVO extends PageParam {
17 17  
18 18 @Schema(description = "功能标识")
19 19 private String identifier;
... ... @@ -22,7 +22,7 @@ public class IotProductThingModelPageReqVO extends PageParam {
22 22 private String name;
23 23  
24 24 @Schema(description = "功能类型", example = "1")
25   - @InEnum(IotProductThingModelTypeEnum.class)
  25 + @InEnum(IotThingModelTypeEnum.class)
26 26 private Integer type;
27 27  
28 28 @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED)
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotProductThingModelRespVO.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelRespVO.java
... ... @@ -13,7 +13,7 @@ import java.time.LocalDateTime;
13 13 @Schema(description = "管理后台 - IoT 产品物模型 Response VO")
14 14 @Data
15 15 @ExcelIgnoreUnannotated
16   -public class IotProductThingModelRespVO {
  16 +public class IotThingModelRespVO {
17 17  
18 18 @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "21816")
19 19 @ExcelProperty("产品ID")
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotProductThingModelSaveReqVO.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelSaveReqVO.java
... ... @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.validation.InEnum;
4 4 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent;
5 5 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
6 6 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
7   -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
  7 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
8 8 import io.swagger.v3.oas.annotations.media.Schema;
9 9 import jakarta.validation.constraints.NotEmpty;
10 10 import jakarta.validation.constraints.NotNull;
... ... @@ -12,7 +12,7 @@ import lombok.Data;
12 12  
13 13 @Schema(description = "管理后台 - IoT 产品物模型新增/修改 Request VO")
14 14 @Data
15   -public class IotProductThingModelSaveReqVO {
  15 +public class IotThingModelSaveReqVO {
16 16  
17 17 @Schema(description = "编号", example = "1")
18 18 private Long id;
... ... @@ -38,7 +38,7 @@ public class IotProductThingModelSaveReqVO {
38 38  
39 39 @Schema(description = "功能类型", requiredMode = Schema.RequiredMode.REQUIRED)
40 40 @NotNull(message = "功能类型不能为空")
41   - @InEnum(IotProductThingModelTypeEnum.class)
  41 + @InEnum(IotThingModelTypeEnum.class)
42 42 private Integer type;
43 43  
44 44 @Schema(description = "属性", requiredMode = Schema.RequiredMode.REQUIRED)
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/thingmodel/IotProductThingModelConvert.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/thingmodel/IotThingModelConvert.java
... ... @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.iot.convert.thingmodel;
3 3 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent;
4 4 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
5 5 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
6   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelRespVO;
7   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelSaveReqVO;
8   -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
9   -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelRespVO;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelSaveReqVO;
  8 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
  9 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
10 10 import org.mapstruct.Mapper;
11 11 import org.mapstruct.Mapping;
12 12 import org.mapstruct.Named;
... ... @@ -16,44 +16,44 @@ import java.util.List;
16 16 import java.util.Objects;
17 17  
18 18 @Mapper
19   -public interface IotProductThingModelConvert {
  19 +public interface IotThingModelConvert {
20 20  
21   - IotProductThingModelConvert INSTANCE = Mappers.getMapper(IotProductThingModelConvert.class);
  21 + IotThingModelConvert INSTANCE = Mappers.getMapper(IotThingModelConvert.class);
22 22  
23 23 // 将 SaveReqVO 转换为 DO
24 24 @Mapping(target = "property", expression = "java(convertToProperty(bean))")
25 25 @Mapping(target = "event", expression = "java(convertToEvent(bean))")
26 26 @Mapping(target = "service", expression = "java(convertToService(bean))")
27   - IotProductThingModelDO convert(IotProductThingModelSaveReqVO bean);
  27 + IotThingModelDO convert(IotThingModelSaveReqVO bean);
28 28  
29 29 // 将 DO 转换为 RespVO
30 30 @Mapping(target = "property", source = "property")
31 31 @Mapping(target = "event", source = "event")
32 32 @Mapping(target = "service", source = "service")
33   - IotProductThingModelRespVO convert(IotProductThingModelDO bean);
  33 + IotThingModelRespVO convert(IotThingModelDO bean);
34 34  
35 35 // 批量转换
36   - List<IotProductThingModelRespVO> convertList(List<IotProductThingModelDO> list);
  36 + List<IotThingModelRespVO> convertList(List<IotThingModelDO> list);
37 37  
38 38 @Named("convertToProperty")
39   - default ThingModelProperty convertToProperty(IotProductThingModelSaveReqVO bean) {
40   - if (Objects.equals(bean.getType(), IotProductThingModelTypeEnum.PROPERTY.getType())) {
  39 + default ThingModelProperty convertToProperty(IotThingModelSaveReqVO bean) {
  40 + if (Objects.equals(bean.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
41 41 return bean.getProperty();
42 42 }
43 43 return null;
44 44 }
45 45  
46 46 @Named("convertToEvent")
47   - default ThingModelEvent convertToEvent(IotProductThingModelSaveReqVO bean) {
48   - if (Objects.equals(bean.getType(), IotProductThingModelTypeEnum.EVENT.getType())) {
  47 + default ThingModelEvent convertToEvent(IotThingModelSaveReqVO bean) {
  48 + if (Objects.equals(bean.getType(), IotThingModelTypeEnum.EVENT.getType())) {
49 49 return bean.getEvent();
50 50 }
51 51 return null;
52 52 }
53 53  
54 54 @Named("convertToService")
55   - default ThingModelService convertToService(IotProductThingModelSaveReqVO bean) {
56   - if (Objects.equals(bean.getType(), IotProductThingModelTypeEnum.SERVICE.getType())) {
  55 + default ThingModelService convertToService(IotThingModelSaveReqVO bean) {
  56 + if (Objects.equals(bean.getType(), IotThingModelTypeEnum.SERVICE.getType())) {
57 57 return bean.getService();
58 58 }
59 59 return null;
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDataDO.java
1 1 package cn.iocoder.yudao.module.iot.dal.dataobject.device;
2 2  
3 3 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
4   -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
  4 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
5 5 import lombok.AllArgsConstructor;
6 6 import lombok.Builder;
7 7 import lombok.Data;
... ... @@ -30,7 +30,7 @@ public class IotDeviceDataDO {
30 30 /**
31 31 * 物模型编号
32 32 * <p>
33   - * 关联 {@link IotProductThingModelDO#getId()}
  33 + * 关联 {@link IotThingModelDO#getId()}
34 34 */
35 35 private Long thingModelId;
36 36  
... ... @@ -51,21 +51,21 @@ public class IotDeviceDataDO {
51 51 /**
52 52 * 属性标识符
53 53 * <p>
54   - * 关联 {@link IotProductThingModelDO#getIdentifier()}
  54 + * 关联 {@link IotThingModelDO#getIdentifier()}
55 55 */
56 56 private String identifier;
57 57  
58 58 /**
59 59 * 属性名称
60 60 * <p>
61   - * 关联 {@link IotProductThingModelDO#getName()}
  61 + * 关联 {@link IotThingModelDO#getName()}
62 62 */
63 63 private String name;
64 64  
65 65 /**
66 66 * 数据类型
67 67 * <p>
68   - * 关联 {@link IotProductThingModelDO#getProperty()#getDataType()}
  68 + * 关联 {@link IotThingModelDO#getProperty()#getDataType()}
69 69 */
70 70 private String dataType;
71 71  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininfo/PluginInfoDO.java
1 1 package cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginDeployTypeEnum;
  5 +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
  6 +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginTypeEnum;
4 7 import com.baomidou.mybatisplus.annotation.KeySequence;
5 8 import com.baomidou.mybatisplus.annotation.TableId;
6 9 import com.baomidou.mybatisplus.annotation.TableName;
... ... @@ -26,11 +29,10 @@ public class PluginInfoDO extends BaseDO {
26 29 */
27 30 @TableId
28 31 private Long id;
29   - // TODO @haohao:这个是不是改成类似 key 之类的字段哈?
30 32 /**
31   - * 插件包 ID
  33 + * 插件包标识符
32 34 */
33   - private String pluginId;
  35 + private String pluginKey;
34 36 /**
35 37 * 插件名称
36 38 */
... ... @@ -41,22 +43,23 @@ public class PluginInfoDO extends BaseDO {
41 43 private String description;
42 44 /**
43 45 * 部署方式
  46 + * <p>
  47 + * 枚举 {@link IotPluginDeployTypeEnum}
44 48 */
45   - // TODO @haohao:枚举
46 49 private Integer deployType;
47 50 /**
48 51 * 插件包文件名
49 52 */
50   - // TODO @haohao:是不是叫 fileName 哈?避免后续有别的字段,类似 fileUrl?
51   - private String file;
  53 + private String fileName;
52 54 /**
53 55 * 插件版本
54 56 */
55 57 private String version;
56 58 /**
57 59 * 插件类型
  60 + * <p>
  61 + * 枚举 {@link IotPluginTypeEnum}
58 62 */
59   - // TODO @haohao:枚举
60 63 private Integer type;
61 64 /**
62 65 * 设备插件协议类型
... ... @@ -64,8 +67,9 @@ public class PluginInfoDO extends BaseDO {
64 67 private String protocol;
65 68 /**
66 69 * 状态
  70 + * <p>
  71 + * 枚举 {@link IotPluginStatusEnum}
67 72 */
68   - // TODO @haohao:枚举
69 73 private Integer status;
70 74 /**
71 75 * 插件配置项描述信息
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java
1 1 package cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
4 5 import com.baomidou.mybatisplus.annotation.KeySequence;
5 6 import com.baomidou.mybatisplus.annotation.TableId;
6 7 import com.baomidou.mybatisplus.annotation.TableName;
7 8 import lombok.*;
8 9  
9   -// TODO @haohao:一些必要的关联、枚举
  10 +
10 11 /**
11 12 * IoT 插件实例 DO
12 13 *
... ... @@ -33,6 +34,8 @@ public class PluginInstanceDO extends BaseDO {
33 34 private String mainId;
34 35 /**
35 36 * 插件id
  37 + * <p>
  38 + * 关联 {@link PluginInfoDO#getId()}
36 39 */
37 40 private Long pluginId;
38 41 /**
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/FieldParser.java
... ... @@ -5,7 +5,8 @@ import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelR
5 5  
6 6 import java.util.HashMap;
7 7 import java.util.List;
8   -import java.util.stream.Collectors;
  8 +
  9 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
9 10  
10 11 /**
11 12 * FieldParser 类用于解析和转换物模型字段到 TDengine 字段
... ... @@ -15,6 +16,7 @@ public class FieldParser {
15 16 /**
16 17 * 物模型到td数据类型映射
17 18 */
  19 + @Deprecated
18 20 private static final HashMap<String, String> TYPE_MAPPING = new HashMap<>() {
19 21 {
20 22 put("INT", "INT");
... ... @@ -52,10 +54,7 @@ public class FieldParser {
52 54 * @return 字段列表
53 55 */
54 56 public static List<TdFieldDO> parse(ThingModelRespVO thingModel) {
55   - // TODO @puhui999:是不是使用 convertList
56   - return thingModel.getModel().getProperties().stream()
57   - .map(FieldParser::parse)
58   - .collect(Collectors.toList());
  57 + return convertList(thingModel.getModel().getProperties(), FieldParser::parse);
59 58 }
60 59  
61 60 /**
... ... @@ -65,18 +64,18 @@ public class FieldParser {
65 64 * @return 转换后的 TDengine 字段对象列表
66 65 */
67 66 public static List<TdFieldDO> parse(List<List<Object>> rows) {
68   - // TODO @puhui999:是不是使用 convertList
69   - return rows.stream().map(row -> {
  67 + return convertList(rows, row -> {
70 68 String type = row.get(1).toString().toUpperCase();
71 69 // TODO @puhui999:"NCHAR" 最好枚举下
72 70 int dataLength = "NCHAR".equals(type) ? Integer.parseInt(row.get(2).toString()) : -1;
73 71 return new TdFieldDO(row.get(0).toString(), type, dataLength);
74   - }).collect(Collectors.toList());
  72 + });
75 73 }
76 74  
77 75 /**
78 76 * 获取字段字义
79 77 */
  78 + @Deprecated
80 79 public static String getFieldDefine(TdFieldDO field) {
81 80 return "`" + field.getFieldName() + "`" + " "
82 81 + (field.getDataLength() > 0 ? String.format("%s(%d)", field.getDataType(), field.getDataLength())
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/SelectDO.java
... ... @@ -2,13 +2,12 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
2 2  
3 3 import lombok.Data;
4 4  
5   -import java.util.Set;
6   -
7 5 // TODO @haohao:类似这个,其实可以参考 mybatis plus,querywrapper,搞个 TdEngineQueryWrapper。这样看起来会更好懂。
8 6 /**
9 7 * 查询DO
10 8 */
11 9 @Data
  10 +@Deprecated
12 11 public class SelectDO {
13 12  
14 13 // TODO @haoha:database 是个单词
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/SelectVisualDO.java
... ... @@ -6,6 +6,7 @@ import java.util.Map;
6 6  
7 7 // TODO @haohao:类似 SelectDO 的想法,只是它是返回。ps:貌似可以在 tdengine 里面,创建一个 query 包,放这种比较特殊的查询和结果对象。dataobject 更多还是实际存储的结构化的 do
8 8 @Data
  9 +@Deprecated
9 10 public class SelectVisualDO {
10 11  
11 12 /**
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TagsSelectDO.java
... ... @@ -6,6 +6,7 @@ import lombok.Data;
6 6 * tags查询DO
7 7 */
8 8 @Data
  9 +@Deprecated
9 10 public class TagsSelectDO {
10 11  
11 12 /**
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdFieldDO.java
... ... @@ -5,6 +5,7 @@ import lombok.Builder;
5 5 import lombok.Data;
6 6 import lombok.NoArgsConstructor;
7 7  
  8 +// TODO 芋艿:看看是不是后续简化掉。
8 9 /**
9 10 * TD 引擎的字段
10 11 */
... ... @@ -12,6 +13,7 @@ import lombok.NoArgsConstructor;
12 13 @NoArgsConstructor
13 14 @AllArgsConstructor
14 15 @Builder
  16 +@Deprecated
15 17 public class TdFieldDO {
16 18  
17 19 /**
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdResponse.java
... ... @@ -12,6 +12,7 @@ import java.util.List;
12 12 @Data
13 13 @NoArgsConstructor
14 14 @AllArgsConstructor
  15 +@Deprecated
15 16 public class TdResponse {
16 17  
17 18 public static final int CODE_SUCCESS = 0;
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdRestApi.java
... ... @@ -13,6 +13,7 @@ import org.springframework.stereotype.Service;
13 13 */
14 14 @Slf4j
15 15 @Service
  16 +@Deprecated // TODO 芋艿:貌似没用到
16 17 public class TdRestApi {
17 18  
18 19 @Value("${spring.datasource.dynamic.datasource.tdengine.url}")
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdTableDO.java
... ... @@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
7 7  
8 8 import java.util.List;
9 9  
  10 +@Deprecated
10 11 /**
11 12 * TD 引擎的数据库
12 13 */
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/IotProductThingModelDO.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/IotThingModelDO.java
... ... @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelE
5 5 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
6 6 import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
8   -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
  8 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
9 9 import com.baomidou.mybatisplus.annotation.KeySequence;
10 10 import com.baomidou.mybatisplus.annotation.TableField;
11 11 import com.baomidou.mybatisplus.annotation.TableId;
... ... @@ -19,17 +19,17 @@ import lombok.NoArgsConstructor;
19 19 /**
20 20 * IoT 产品物模型功能 DO
21 21 * <p>
22   - * 每个 {@link IotProductDO} 和 {@link IotProductThingModelDO} 是“一对多”的关系,它的每个属性、事件、服务都对应一条记录
  22 + * 每个 {@link IotProductDO} 和 {@link IotThingModelDO} 是“一对多”的关系,它的每个属性、事件、服务都对应一条记录
23 23 *
24 24 * @author 芋道源码
25 25 */
26   -@TableName(value = "iot_product_thing_model", autoResultMap = true)
27   -@KeySequence("iot_product_thing_model_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  26 +@TableName(value = "iot_thing_model", autoResultMap = true)
  27 +@KeySequence("iot_thing_model_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
28 28 @Data
29 29 @Builder
30 30 @NoArgsConstructor
31 31 @AllArgsConstructor
32   -public class IotProductThingModelDO extends BaseDO {
  32 +public class IotThingModelDO extends BaseDO {
33 33  
34 34 /**
35 35 * 物模型功能编号
... ... @@ -66,7 +66,7 @@ public class IotProductThingModelDO extends BaseDO {
66 66 /**
67 67 * 功能类型
68 68 * <p>
69   - * 枚举 {@link IotProductThingModelTypeEnum}
  69 + * 枚举 {@link IotThingModelTypeEnum}
70 70 */
71 71 private Integer type;
72 72  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 7 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
8 8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  9 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
9 10 import org.apache.ibatis.annotations.Mapper;
10 11  
11 12 import java.util.List;
... ... @@ -47,7 +48,8 @@ public interface IotDeviceMapper extends BaseMapperX&lt;IotDeviceDO&gt; {
47 48 }
48 49  
49 50 default IotDeviceDO selectByDeviceKey(String deviceKey) {
50   - return selectOne(IotDeviceDO::getDeviceKey, deviceKey);
  51 + return selectOne(new LambdaQueryWrapper<IotDeviceDO>()
  52 + .apply("LOWER(device_key) = {0}", deviceKey.toLowerCase()));
51 53 }
52 54  
53 55 default List<IotDeviceDO> selectList(Integer deviceType) {
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugininfo/PluginInfoMapper.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java
1   -package cn.iocoder.yudao.module.iot.dal.mysql.plugininfo;
  1 +package cn.iocoder.yudao.module.iot.dal.mysql.plugin;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 6 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
7 7 import org.apache.ibatis.annotations.Mapper;
8   -import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.*;
  8 +import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.*;
9 9  
10 10 /**
11 11 * IoT 插件信息 Mapper
... ... @@ -17,19 +17,8 @@ public interface PluginInfoMapper extends BaseMapperX&lt;PluginInfoDO&gt; {
17 17  
18 18 default PageResult<PluginInfoDO> selectPage(PluginInfoPageReqVO reqVO) {
19 19 return selectPage(reqVO, new LambdaQueryWrapperX<PluginInfoDO>()
20   - .eqIfPresent(PluginInfoDO::getPluginId, reqVO.getPluginId())
21 20 .likeIfPresent(PluginInfoDO::getName, reqVO.getName())
22   - .eqIfPresent(PluginInfoDO::getDescription, reqVO.getDescription())
23   - .eqIfPresent(PluginInfoDO::getDeployType, reqVO.getDeployType())
24   - .eqIfPresent(PluginInfoDO::getFile, reqVO.getFile())
25   - .eqIfPresent(PluginInfoDO::getVersion, reqVO.getVersion())
26   - .eqIfPresent(PluginInfoDO::getType, reqVO.getType())
27   - .eqIfPresent(PluginInfoDO::getProtocol, reqVO.getProtocol())
28 21 .eqIfPresent(PluginInfoDO::getStatus, reqVO.getStatus())
29   - .eqIfPresent(PluginInfoDO::getConfigSchema, reqVO.getConfigSchema())
30   - .eqIfPresent(PluginInfoDO::getConfig, reqVO.getConfig())
31   - .eqIfPresent(PluginInfoDO::getScript, reqVO.getScript())
32   - .betweenIfPresent(PluginInfoDO::getCreateTime, reqVO.getCreateTime())
33 22 .orderByDesc(PluginInfoDO::getId));
34 23 }
35 24  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugininstance/PluginInstanceMapper.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java
1   -package cn.iocoder.yudao.module.iot.dal.mysql.plugininstance;
  1 +package cn.iocoder.yudao.module.iot.dal.mysql.plugin;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInstancePageReqVO;
6 7 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
7 8 import org.apache.ibatis.annotations.Mapper;
8   -import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*;
9 9  
10 10 /**
11 11 * IoT 插件实例 Mapper
... ... @@ -15,6 +15,12 @@ import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*;
15 15 @Mapper
16 16 public interface PluginInstanceMapper extends BaseMapperX<PluginInstanceDO> {
17 17  
  18 + default PluginInstanceDO selectByMainIdAndPluginId(String mainId, Long pluginId) {
  19 + return selectOne(new LambdaQueryWrapperX<PluginInstanceDO>()
  20 + .eq(PluginInstanceDO::getMainId, mainId)
  21 + .eq(PluginInstanceDO::getPluginId, pluginId));
  22 + }
  23 +
18 24 default PageResult<PluginInstanceDO> selectPage(PluginInstancePageReqVO reqVO) {
19 25 return selectPage(reqVO, new LambdaQueryWrapperX<PluginInstanceDO>()
20 26 .eqIfPresent(PluginInstanceDO::getMainId, reqVO.getMainId())
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO;
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
  8 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
8 9 import org.apache.ibatis.annotations.Mapper;
9 10  
10 11 /**
... ... @@ -23,7 +24,8 @@ public interface IotProductMapper extends BaseMapperX&lt;IotProductDO&gt; {
23 24 }
24 25  
25 26 default IotProductDO selectByProductKey(String productKey) {
26   - return selectOne(IotProductDO::getProductKey, productKey);
  27 + return selectOne(new LambdaQueryWrapper<IotProductDO>()
  28 + .apply("LOWER(product_key) = {0}", productKey.toLowerCase()));
27 29 }
28 30  
29 31 }
30 32 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/thingmodel/IotProductThingModelMapper.java deleted
1   -package cn.iocoder.yudao.module.iot.dal.mysql.thingmodel;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5   -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelPageReqVO;
7   -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
8   -import org.apache.ibatis.annotations.Mapper;
9   -
10   -import java.util.List;
11   -
12   -/**
13   - * IoT 产品物模型 Mapper
14   - *
15   - * @author 芋道源码
16   - */
17   -@Mapper
18   -public interface IotProductThingModelMapper extends BaseMapperX<IotProductThingModelDO> {
19   -
20   - default PageResult<IotProductThingModelDO> selectPage(IotProductThingModelPageReqVO reqVO) {
21   - return selectPage(reqVO, new LambdaQueryWrapperX<IotProductThingModelDO>()
22   - .eqIfPresent(IotProductThingModelDO::getIdentifier, reqVO.getIdentifier())
23   - .likeIfPresent(IotProductThingModelDO::getName, reqVO.getName())
24   - .eqIfPresent(IotProductThingModelDO::getType, reqVO.getType())
25   - .eqIfPresent(IotProductThingModelDO::getProductId, reqVO.getProductId())
26   - .notIn(IotProductThingModelDO::getIdentifier, "get", "set", "post")
27   - .orderByDesc(IotProductThingModelDO::getId));
28   - }
29   -
30   - default IotProductThingModelDO selectByProductIdAndIdentifier(Long productId, String identifier) {
31   - return selectOne(IotProductThingModelDO::getProductId, productId,
32   - IotProductThingModelDO::getIdentifier, identifier);
33   - }
34   -
35   - default List<IotProductThingModelDO> selectListByProductId(Long productId) {
36   - return selectList(IotProductThingModelDO::getProductId, productId);
37   - }
38   -
39   - default List<IotProductThingModelDO> selectListByProductIdAndType(Long productId, Integer type) {
40   - return selectList(IotProductThingModelDO::getProductId, productId,
41   - IotProductThingModelDO::getType, type);
42   - }
43   -
44   - default List<IotProductThingModelDO> selectListByProductIdAndIdentifiersAndTypes(Long productId,
45   - List<String> identifiers,
46   - List<Integer> types) {
47   - return selectList(new LambdaQueryWrapperX<IotProductThingModelDO>()
48   - .eq(IotProductThingModelDO::getProductId, productId)
49   - .in(IotProductThingModelDO::getIdentifier, identifiers)
50   - .in(IotProductThingModelDO::getType, types));
51   - }
52   -
53   - default IotProductThingModelDO selectByProductIdAndName(Long productId, String name) {
54   - return selectOne(IotProductThingModelDO::getProductId, productId,
55   - IotProductThingModelDO::getName, name);
56   - }
57   -
58   - default List<IotProductThingModelDO> selectListByProductKey(String productKey) {
59   - return selectList(IotProductThingModelDO::getProductKey, productKey);
60   - }
61   -
62   -}
63 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/thingmodel/IotThingModelMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.mysql.thingmodel;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelPageReqVO;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * IoT 产品物模型 Mapper
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Mapper
  18 +public interface IotThingModelMapper extends BaseMapperX<IotThingModelDO> {
  19 +
  20 + default PageResult<IotThingModelDO> selectPage(IotThingModelPageReqVO reqVO) {
  21 + return selectPage(reqVO, new LambdaQueryWrapperX<IotThingModelDO>()
  22 + .eqIfPresent(IotThingModelDO::getIdentifier, reqVO.getIdentifier())
  23 + .likeIfPresent(IotThingModelDO::getName, reqVO.getName())
  24 + .eqIfPresent(IotThingModelDO::getType, reqVO.getType())
  25 + .eqIfPresent(IotThingModelDO::getProductId, reqVO.getProductId())
  26 + // TODO @芋艿:看看要不要加枚举
  27 + .notIn(IotThingModelDO::getIdentifier, "get", "set", "post")
  28 + .orderByDesc(IotThingModelDO::getId));
  29 + }
  30 +
  31 + default IotThingModelDO selectByProductIdAndIdentifier(Long productId, String identifier) {
  32 + return selectOne(IotThingModelDO::getProductId, productId,
  33 + IotThingModelDO::getIdentifier, identifier);
  34 + }
  35 +
  36 + default List<IotThingModelDO> selectListByProductId(Long productId) {
  37 + return selectList(IotThingModelDO::getProductId, productId);
  38 + }
  39 +
  40 + default List<IotThingModelDO> selectListByProductIdAndType(Long productId, Integer type) {
  41 + return selectList(IotThingModelDO::getProductId, productId,
  42 + IotThingModelDO::getType, type);
  43 + }
  44 +
  45 + default List<IotThingModelDO> selectListByProductIdAndIdentifiersAndTypes(Long productId,
  46 + List<String> identifiers,
  47 + List<Integer> types) {
  48 + return selectList(new LambdaQueryWrapperX<IotThingModelDO>()
  49 + .eq(IotThingModelDO::getProductId, productId)
  50 + .in(IotThingModelDO::getIdentifier, identifiers)
  51 + .in(IotThingModelDO::getType, types));
  52 + }
  53 +
  54 + default IotThingModelDO selectByProductIdAndName(Long productId, String name) {
  55 + return selectOne(IotThingModelDO::getProductId, productId,
  56 + IotThingModelDO::getName, name);
  57 + }
  58 +
  59 + default List<IotThingModelDO> selectListByProductKey(String productKey) {
  60 + return selectList(IotThingModelDO::getProductKey, productKey);
  61 + }
  62 +
  63 +}
0 64 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDevicePropertyDataMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.tdengine;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
  5 +import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
  6 +import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS;
  7 +import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +import org.apache.ibatis.annotations.Param;
  10 +
  11 +import java.util.ArrayList;
  12 +import java.util.List;
  13 +import java.util.stream.Collectors;
  14 +
  15 +@Mapper
  16 +@TDengineDS
  17 +@InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错
  18 +public interface IotDevicePropertyDataMapper {
  19 +
  20 + List<TDengineTableField> getProductPropertySTableFieldList(@Param("productKey") String productKey);
  21 +
  22 + void createProductPropertySTable(@Param("productKey") String productKey,
  23 + @Param("fields") List<TDengineTableField> fields);
  24 +
  25 + @SuppressWarnings("SimplifyStreamApiCallChains") // 保持 JDK8 兼容性
  26 + default void alterProductPropertySTable(String productKey,
  27 + List<TDengineTableField> oldFields,
  28 + List<TDengineTableField> newFields) {
  29 + oldFields.removeIf(field -> TDengineTableField.FIELD_TS.equals(field.getField())
  30 + || TDengineTableField.FIELD_DEVICE_KEY.equals(field.getField()));
  31 + List<TDengineTableField> addFields = newFields.stream().filter( // 新增的字段
  32 + newField -> oldFields.stream().noneMatch(oldField -> oldField.getField().equals(newField.getField())))
  33 + .collect(Collectors.toList());
  34 + List<TDengineTableField> dropFields = oldFields.stream().filter( // 删除的字段
  35 + oldField -> newFields.stream().noneMatch(n -> n.getField().equals(oldField.getField())))
  36 + .collect(Collectors.toList());
  37 + List<TDengineTableField> modifyTypeFields = new ArrayList<>(); // 变更类型的字段
  38 + List<TDengineTableField> modifyLengthFields = new ArrayList<>(); // 变更长度的字段
  39 + newFields.forEach(newField -> {
  40 + TDengineTableField oldField = CollUtil.findOne(oldFields, field -> field.getField().equals(newField.getField()));
  41 + if (oldField == null) {
  42 + return;
  43 + }
  44 + if (ObjectUtil.notEqual(oldField.getType(), newField.getType())) {
  45 + modifyTypeFields.add(newField);
  46 + return;
  47 + }
  48 + if (newField.getLength()!= null) {
  49 + if (newField.getLength() > oldField.getLength()) {
  50 + modifyLengthFields.add(newField);
  51 + } else if (newField.getLength() < oldField.getLength()) {
  52 + // 特殊:TDengine 长度修改时,只允许变长,所以此时认为是修改类型
  53 + modifyTypeFields.add(newField);
  54 + }
  55 + }
  56 + });
  57 +
  58 + // 执行
  59 + addFields.forEach(field -> alterProductPropertySTableAddField(productKey, field));
  60 + dropFields.forEach(field -> alterProductPropertySTableDropField(productKey, field));
  61 + modifyLengthFields.forEach(field -> alterProductPropertySTableModifyField(productKey, field));
  62 + modifyTypeFields.forEach(field -> {
  63 + alterProductPropertySTableDropField(productKey, field);
  64 + alterProductPropertySTableAddField(productKey, field);
  65 + });
  66 + }
  67 +
  68 + void alterProductPropertySTableAddField(@Param("productKey") String productKey,
  69 + @Param("field") TDengineTableField field);
  70 +
  71 + void alterProductPropertySTableModifyField(@Param("productKey") String productKey,
  72 + @Param("field") TDengineTableField field);
  73 +
  74 + void alterProductPropertySTableDropField(@Param("productKey") String productKey,
  75 + @Param("field") TDengineTableField field);
  76 +
  77 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdEngineDDLMapper.java
... ... @@ -96,7 +96,6 @@ public interface TdEngineDDLMapper {
96 96 @TenantIgnore
97 97 void modifyColumnWidthForSuperTable(TdTableDO superTable);
98 98  
99   -
100 99 /**
101 100 * 修改超级表 - 为超级表添加标签
102 101 *
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java
1 1 package cn.iocoder.yudao.module.iot.emq.service;
2 2  
3   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4   -import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
  3 +import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
5 4 import jakarta.annotation.Resource;
6 5 import lombok.extern.slf4j.Slf4j;
7 6 import org.eclipse.paho.client.mqttv3.MqttClient;
... ... @@ -21,7 +20,7 @@ import org.springframework.stereotype.Service;
21 20 public class EmqxServiceImpl implements EmqxService {
22 21  
23 22 @Resource
24   - private IotDeviceDataService iotDeviceDataService;
  23 + private IotDevicePropertyDataService iotDeviceDataService;
25 24  
26 25 // TODO 多线程处理消息
27 26 @Override
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java deleted
1   -package cn.iocoder.yudao.module.iot.framework.plugin;
2   -
3   -import org.pf4j.spring.SpringPluginManager;
4   -import org.springframework.context.annotation.Bean;
5   -import org.springframework.context.annotation.Configuration;
6   -import org.springframework.context.annotation.DependsOn;
7   -
8   -@Configuration
9   -public class SpringConfiguration {
10   -
11   - @Bean
12   - @DependsOn("serviceRegistryInitializedMarker")
13   - public SpringPluginManager pluginManager() {
14   - return new SpringPluginManager();
15   - }
16   -
17   -}
18 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/ServiceRegistryConfiguration.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java
1 1 package cn.iocoder.yudao.module.iot.framework.plugin;
2 2  
3   -import cn.iocoder.yudao.module.iot.api.DeviceDataApi;
4 3 import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
  4 +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
  5 +import cn.iocoder.yudao.module.iot.framework.plugin.listener.CustomPluginStateListener;
5 6 import lombok.extern.slf4j.Slf4j;
  7 +import org.pf4j.spring.SpringPluginManager;
6 8 import org.springframework.context.annotation.Bean;
7 9 import org.springframework.context.annotation.Configuration;
  10 +import org.springframework.context.annotation.DependsOn;
8 11  
9   -import javax.annotation.PostConstruct;
10 12 import javax.annotation.Resource;
11 13  
12 14 @Slf4j
13 15 @Configuration
14   -public class ServiceRegistryConfiguration {
  16 +public class UnifiedConfiguration {
  17 +
  18 + private static final String SERVICE_REGISTRY_INITIALIZED_MARKER = "serviceRegistryInitializedMarker";
15 19  
16 20 @Resource
17 21 private DeviceDataApi deviceDataApi;
18 22  
19   - @PostConstruct
20   - public void init() {
21   - // 将主程序中的 DeviceDataApi 实例注册到 ServiceRegistry
  23 + @Bean(SERVICE_REGISTRY_INITIALIZED_MARKER)
  24 + public Object serviceRegistryInitializedMarker() {
22 25 ServiceRegistry.registerService(DeviceDataApi.class, deviceDataApi);
23 26 log.info("[init][将 DeviceDataApi 实例注册到 ServiceRegistry 中]");
  27 + return new Object();
24 28 }
25 29  
26   - /**
27   - * 定义一个标记用的 Bean,用于表示 ServiceRegistry 已初始化完成
28   - */
29   - @Bean("serviceRegistryInitializedMarker")
30   - public Object serviceRegistryInitializedMarker() {
31   - // 返回任意对象即可,这里返回null都可以,但最好返回个实际对象
32   - return new Object();
  30 + @Bean
  31 + @DependsOn(SERVICE_REGISTRY_INITIALIZED_MARKER)
  32 + public SpringPluginManager pluginManager() {
  33 + log.info("[init][实例化 SpringPluginManager]");
  34 + SpringPluginManager springPluginManager = new SpringPluginManager();
  35 + springPluginManager.addPluginStateListener(new CustomPluginStateListener());
  36 + return springPluginManager;
33 37 }
  38 +
34 39 }
35 40 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/listener/CustomPluginStateListener.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.plugin.listener;
  2 +
  3 +import lombok.extern.slf4j.Slf4j;
  4 +import org.pf4j.PluginStateEvent;
  5 +import org.pf4j.PluginStateListener;
  6 +import org.springframework.stereotype.Component;
  7 +
  8 +@Component
  9 +@Slf4j
  10 +public class CustomPluginStateListener implements PluginStateListener {
  11 +
  12 + @Override
  13 + public void pluginStateChanged(PluginStateEvent event) {
  14 + // 1. 获取插件ID
  15 + String pluginId = event.getPlugin().getPluginId();
  16 + // 2. 获取插件旧状态
  17 + String oldState = event.getOldState().toString();
  18 + // 3. 获取插件新状态
  19 + String newState = event.getPluginState().toString();
  20 + // 4. 打印日志信息
  21 + log.info("插件的状态 '{}' 已更改为 '{}' 至 '{}'", pluginId, oldState, newState);
  22 + }
  23 +
  24 +}
0 25 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/core/TDengineTableField.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.tdengine.core;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +/**
  8 + * TDEngine 表字段
  9 + */
  10 +@Data
  11 +@NoArgsConstructor
  12 +@AllArgsConstructor
  13 +public class TDengineTableField {
  14 +
  15 + /**
  16 + * 字段名 - TDengine 默认 ts 字段,默认会被 TDengine 创建
  17 + */
  18 + public static final String FIELD_TS = "ts";
  19 +
  20 + /**
  21 + * 字段名 - 我们系统定义的 device_key 字段,非 TDengine 默认字段
  22 + */
  23 + public static final String FIELD_DEVICE_KEY = "device_key";
  24 +
  25 + public static final String TYPE_TINYINT = "TINYINT";
  26 + public static final String TYPE_INT = "INT";
  27 + public static final String TYPE_FLOAT = "FLOAT";
  28 + public static final String TYPE_DOUBLE = "DOUBLE";
  29 + public static final String TYPE_BOOL = "BOOL";
  30 + public static final String TYPE_NCHAR = "NCHAR";
  31 + public static final String TYPE_TIMESTAMP = "TIMESTAMP";
  32 +
  33 + /**
  34 + * 注释 - TAG 字段
  35 + */
  36 + public static final String NOTE_TAG = "TAG";
  37 +
  38 + /**
  39 + * 字段名
  40 + */
  41 + private String field;
  42 +
  43 + /**
  44 + * 字段类型
  45 + */
  46 + private String type;
  47 +
  48 + /**
  49 + * 字段长度
  50 + */
  51 + private Integer length;
  52 +
  53 + /**
  54 + * 注释
  55 + */
  56 + private String note;
  57 +
  58 + public TDengineTableField(String field, String type) {
  59 + this.field = field;
  60 + this.type = type;
  61 + }
  62 +
  63 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/core/annotation/TDengineDS.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation;
  2 +
  3 +import com.baomidou.dynamic.datasource.annotation.DS;
  4 +
  5 +import java.lang.annotation.*;
  6 +
  7 +/**
  8 + * TDEngine 数据源
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Target({ElementType.TYPE, ElementType.METHOD})
  13 +@Retention(RetentionPolicy.RUNTIME)
  14 +@Documented
  15 +@DS("tdengine")
  16 +public @interface TDengineDS {
  17 +}
0 18 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/package-info.java 0 → 100644
  1 +/**
  2 + * iot 模块的 tdengine 拓展封装
  3 + */
  4 +package cn.iocoder.yudao.module.iot.framework.tdengine;
0 5 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.job.plugin;
  2 +
  3 +
  4 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
  5 +import cn.iocoder.yudao.module.iot.service.plugin.PluginInstanceService;
  6 +import org.springframework.scheduling.annotation.Scheduled;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.annotation.Resource;
  10 +import java.util.concurrent.TimeUnit;
  11 +
  12 +/**
  13 + * 插件实例 Job
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Component
  18 +public class PluginInstancesJob {
  19 +
  20 + @Resource
  21 + private PluginInstanceService pluginInstanceService;
  22 +
  23 + @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
  24 + public void updatePluginInstances() {
  25 + TenantUtils.executeIgnore(() -> {
  26 + pluginInstanceService.updatePluginInstances();
  27 + });
  28 + }
  29 +}
0 30 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/simulatesend/SimulateSendConsumer.java
1 1 package cn.iocoder.yudao.module.iot.mq.consumer.simulatesend;
2 2  
3 3 /**
  4 + * TODO @alwayssuper:记得实现,还有类注释哈
  5 + *
4 6 * @author alwayssuper
5   - * @date 2024/12/20 8:04
  7 + * @since 2024/12/20 8:04
6 8 */
7 9 public class SimulateSendConsumer {
8 10 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.iot.service.device;
2   -
3   -import cn.hutool.core.date.DateUtil;
4   -import cn.hutool.core.util.StrUtil;
5   -import cn.hutool.json.JSONObject;
6   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
7   -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO;
8   -import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
9   -import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
10   -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO;
11   -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
12   -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
13   -import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
14   -import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
15   -import cn.iocoder.yudao.module.iot.enums.IotConstants;
16   -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
17   -import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
18   -import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
19   -import jakarta.annotation.Resource;
20   -import jakarta.validation.Valid;
21   -import lombok.extern.slf4j.Slf4j;
22   -import org.springframework.beans.factory.annotation.Value;
23   -import org.springframework.stereotype.Service;
24   -
25   -import java.time.ZoneId;
26   -import java.util.ArrayList;
27   -import java.util.HashMap;
28   -import java.util.List;
29   -import java.util.Map;
30   -
31   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
32   -
33   -@Slf4j
34   -@Service
35   -public class IotDeviceDataServiceImpl implements IotDeviceDataService {
36   -
37   - @Value("${spring.datasource.dynamic.datasource.tdengine.url}")
38   - private String url;
39   -
40   - @Resource
41   - private IotDeviceService deviceService;
42   - @Resource
43   - private IotThingModelMessageService thingModelMessageService;
44   - @Resource
45   - private IotProductThingModelService thingModelService;
46   - @Resource
47   - private TdEngineDMLMapper tdEngineDMLMapper;
48   -
49   - @Resource
50   - private DeviceDataRedisDAO deviceDataRedisDAO;
51   -
52   - @Override
53   - public void saveDeviceData(String productKey, String deviceName, String message) {
54   - // 1. 根据产品 key 和设备名称,获得设备信息
55   - IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(productKey, deviceName);
56   - // 2. 解析消息,保存数据
57   - JSONObject jsonObject = new JSONObject(message);
58   - log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", productKey, deviceName, jsonObject);
59   - ThingModelMessage thingModelMessage = ThingModelMessage.builder()
60   - .id(jsonObject.getStr("id"))
61   - .sys(jsonObject.get("sys"))
62   - .method(jsonObject.getStr("method"))
63   - .params(jsonObject.get("params"))
64   - .time(jsonObject.getLong("time") == null ? System.currentTimeMillis() : jsonObject.getLong("time"))
65   - .productKey(productKey)
66   - .deviceName(deviceName)
67   - .deviceKey(device.getDeviceKey())
68   - .build();
69   - thingModelMessageService.saveThingModelMessage(device, thingModelMessage);
70   - }
71   -
72   - @Override
73   - public List<IotDeviceDataDO> getLatestDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO) {
74   - List<IotDeviceDataDO> list = new ArrayList<>();
75   - // 1. 获取设备信息
76   - IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
77   - // 2. 获取设备属性最新数据
78   - List<IotProductThingModelDO> thingModelList = thingModelService.getProductThingModelListByProductKey(device.getProductKey());
79   - thingModelList = filterList(thingModelList, thingModel -> IotProductThingModelTypeEnum.PROPERTY.getType()
80   - .equals(thingModel.getType()));
81   -
82   - // 3. 过滤标识符和属性名称
83   - if (deviceDataReqVO.getIdentifier() != null) {
84   - thingModelList = filterList(thingModelList, thingModel -> thingModel.getIdentifier()
85   - .toLowerCase().contains(deviceDataReqVO.getIdentifier().toLowerCase()));
86   - }
87   - if (deviceDataReqVO.getName() != null) {
88   - thingModelList = filterList(thingModelList, thingModel -> thingModel.getName()
89   - .toLowerCase().contains(deviceDataReqVO.getName().toLowerCase()));
90   - }
91   - // 4. 获取设备属性最新数据
92   - thingModelList.forEach(thingModel -> {
93   - IotDeviceDataDO deviceData = deviceDataRedisDAO.get(device.getProductKey(), device.getDeviceName(), thingModel.getIdentifier());
94   - if (deviceData == null) {
95   - deviceData = new IotDeviceDataDO();
96   - deviceData.setProductKey(device.getProductKey());
97   - deviceData.setDeviceName(device.getDeviceName());
98   - deviceData.setIdentifier(thingModel.getIdentifier());
99   - deviceData.setDeviceId(deviceDataReqVO.getDeviceId());
100   - deviceData.setThingModelId(thingModel.getId());
101   - deviceData.setName(thingModel.getName());
102   - deviceData.setDataType(thingModel.getProperty().getDataType());
103   - }
104   - list.add(deviceData);
105   - });
106   - return list;
107   - }
108   -
109   - @Override
110   - public PageResult<Map<String, Object>> getHistoryDeviceProperties(IotDeviceDataPageReqVO deviceDataReqVO) {
111   - PageResult<Map<String, Object>> pageResult = new PageResult<>();
112   - // 1. 获取设备信息
113   - IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
114   - // 2. 获取设备属性历史数据
115   - SelectVisualDO selectVisualDO = new SelectVisualDO();
116   - selectVisualDO.setDataBaseName(getDatabaseName());
117   - selectVisualDO.setTableName(getDeviceTableName(device.getProductKey(), device.getDeviceName()));
118   - selectVisualDO.setFieldName(deviceDataReqVO.getIdentifier());
119   - selectVisualDO.setStartTime(DateUtil.date(deviceDataReqVO.getTimes()[0].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()).getTime());
120   - selectVisualDO.setEndTime(DateUtil.date(deviceDataReqVO.getTimes()[1].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()).getTime());
121   - Map<String, Object> params = new HashMap<>();
122   - params.put("rows", deviceDataReqVO.getPageSize());
123   - params.put("page", (deviceDataReqVO.getPageNo() - 1) * deviceDataReqVO.getPageSize());
124   - selectVisualDO.setParams(params);
125   - pageResult.setList(tdEngineDMLMapper.selectHistoryDataList(selectVisualDO));
126   - pageResult.setTotal(tdEngineDMLMapper.selectHistoryCount(selectVisualDO));
127   - return pageResult;
128   - }
129   -
130   - private String getDatabaseName() {
131   - return StrUtil.subAfter(url, "/", true);
132   - }
133   -
134   - private static String getDeviceTableName(String productKey, String deviceName) {
135   - return String.format(IotConstants.DEVICE_TABLE_NAME_FORMAT, productKey, deviceName);
136   - }
137   -
138   -}
139 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataService.java
... ... @@ -9,11 +9,18 @@ import java.util.List;
9 9 import java.util.Map;
10 10  
11 11 /**
12   - * IoT 设备数据 Service 接口
  12 + * IoT 设备【属性】数据 Service 接口
13 13 *
14 14 * @author 芋道源码
15 15 */
16   -public interface IotDeviceDataService {
  16 +public interface IotDevicePropertyDataService {
  17 +
  18 + /**
  19 + * 定义设备属性数据的结构
  20 + *
  21 + * @param productId 产品编号
  22 + */
  23 + void defineDevicePropertyData(Long productId);
17 24  
18 25 /**
19 26 * 保存设备数据
... ... @@ -21,7 +28,7 @@ public interface IotDeviceDataService {
21 28 * @param productKey 产品 key
22 29 * @param deviceName 设备名称
23 30 * @param message 消息
24   - * <p>JSON 格式,参见 <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">...</a>
  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>
25 32 */
26 33 void saveDeviceData(String productKey, String deviceName, String message);
27 34  
... ... @@ -40,4 +47,5 @@ public interface IotDeviceDataService {
40 47 * @return 设备属性历史数据
41 48 */
42 49 PageResult<Map<String, Object>> getHistoryDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO);
  50 +
43 51 }
44 52 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.device;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.date.DateUtil;
  5 +import cn.hutool.core.map.MapUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import cn.hutool.json.JSONObject;
  8 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  9 +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.dal.dataobject.device.IotDeviceDO;
  12 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
  13 +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.ThingModelMessage;
  16 +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;
  19 +import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
  20 +import cn.iocoder.yudao.module.iot.enums.IotConstants;
  21 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
  22 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
  23 +import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
  24 +import cn.iocoder.yudao.module.iot.service.product.IotProductService;
  25 +import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
  26 +import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
  27 +import jakarta.annotation.Resource;
  28 +import jakarta.validation.Valid;
  29 +import lombok.extern.slf4j.Slf4j;
  30 +import org.springframework.beans.factory.annotation.Value;
  31 +import org.springframework.stereotype.Service;
  32 +
  33 +import java.time.ZoneId;
  34 +import java.util.ArrayList;
  35 +import java.util.HashMap;
  36 +import java.util.List;
  37 +import java.util.Map;
  38 +
  39 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  40 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
  41 +
  42 +/**
  43 + * IoT 设备【属性】数据 Service 实现类
  44 + *
  45 + * @author 芋道源码
  46 + */
  47 +@Service
  48 +@Slf4j
  49 +public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataService {
  50 +
  51 + /**
  52 + * 物模型的数据类型,与 TDengine 数据类型的映射关系
  53 + */
  54 + private static final Map<String, String> TYPE_MAPPING = MapUtil.<String, String>builder()
  55 + .put(IotDataSpecsDataTypeEnum.INT.getDataType(), TDengineTableField.TYPE_INT)
  56 + .put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT)
  57 + .put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE)
  58 + .put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明?
  59 + .put( IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明?
  60 + .put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_NCHAR)
  61 + .put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP)
  62 + .put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!!
  63 + .put(IotDataSpecsDataTypeEnum.ARRAY.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!!
  64 + .build();
  65 +
  66 + @Value("${spring.datasource.dynamic.datasource.tdengine.url}")
  67 + private String url;
  68 +
  69 + @Resource
  70 + private IotDeviceService deviceService;
  71 + @Resource
  72 + private IotThingModelMessageService thingModelMessageService;
  73 + @Resource
  74 + private IotThingModelService thingModelService;
  75 + @Resource
  76 + private IotProductService productService;
  77 +
  78 + @Resource
  79 + private TdEngineDMLMapper tdEngineDMLMapper;
  80 +
  81 + @Resource
  82 + private DeviceDataRedisDAO deviceDataRedisDAO;
  83 +
  84 + @Resource
  85 + private IotDevicePropertyDataMapper devicePropertyDataMapper;
  86 +
  87 + @Override
  88 + public void defineDevicePropertyData(Long productId) {
  89 + // 1.1 查询产品和物模型
  90 + IotProductDO product = productService.validateProductExists(productId);
  91 + List<IotThingModelDO> thingModels = filterList(thingModelService.getThingModelListByProductId(productId),
  92 + thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
  93 + // 1.2 解析 DB 里的字段
  94 + List<TDengineTableField> oldFields = new ArrayList<>();
  95 + try {
  96 + oldFields.addAll(devicePropertyDataMapper.getProductPropertySTableFieldList(product.getProductKey()));
  97 + } catch (Exception e) {
  98 + if (!e.getMessage().contains("Table does not exist")) {
  99 + throw e;
  100 + }
  101 + }
  102 +
  103 + // 2.1 情况一:如果是新增的时候,需要创建表
  104 + List<TDengineTableField> newFields = buildTableFieldList(thingModels);
  105 + if (CollUtil.isEmpty(oldFields)) {
  106 + if (CollUtil.isEmpty(newFields)) {
  107 + log.info("[defineDevicePropertyData][productId({}) 没有需要定义的属性]", productId);
  108 + return;
  109 + }
  110 + newFields.add(0, new TDengineTableField(TDengineTableField.FIELD_TS, TDengineTableField.TYPE_TIMESTAMP));
  111 + devicePropertyDataMapper.createProductPropertySTable(product.getProductKey(), newFields);
  112 + return;
  113 + }
  114 + // 2.2 情况二:如果是修改的时候,需要更新表
  115 + devicePropertyDataMapper.alterProductPropertySTable(product.getProductKey(), oldFields, newFields);
  116 + }
  117 +
  118 + private List<TDengineTableField> buildTableFieldList(List<IotThingModelDO> thingModels) {
  119 + return convertList(thingModels, thingModel -> {
  120 + TDengineTableField field = new TDengineTableField(
  121 + StrUtil.toUnderlineCase(thingModel.getIdentifier()), // TDengine 字段默认都是小写
  122 + TYPE_MAPPING.get(thingModel.getProperty().getDataType()));
  123 + if (thingModel.getProperty().getDataType().equals(IotDataSpecsDataTypeEnum.TEXT.getDataType())) {
  124 + field.setLength(((ThingModelDateOrTextDataSpecs) thingModel.getProperty().getDataSpecs()).getLength());
  125 + }
  126 + return field;
  127 + });
  128 + }
  129 +
  130 + @Override
  131 + public void saveDeviceData(String productKey, String deviceName, String message) {
  132 + // 1. 根据产品 key 和设备名称,获得设备信息
  133 + IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(productKey, deviceName);
  134 + // 2. 解析消息,保存数据
  135 + JSONObject jsonObject = new JSONObject(message);
  136 + log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", productKey, deviceName, jsonObject);
  137 + ThingModelMessage thingModelMessage = ThingModelMessage.builder()
  138 + .id(jsonObject.getStr("id"))
  139 + .sys(jsonObject.get("sys"))
  140 + .method(jsonObject.getStr("method"))
  141 + .params(jsonObject.get("params"))
  142 + .time(jsonObject.getLong("time") == null ? System.currentTimeMillis() : jsonObject.getLong("time"))
  143 + .productKey(productKey)
  144 + .deviceName(deviceName)
  145 + .deviceKey(device.getDeviceKey())
  146 + .build();
  147 + thingModelMessageService.saveThingModelMessage(device, thingModelMessage);
  148 + }
  149 +
  150 + @Override
  151 + public List<IotDeviceDataDO> getLatestDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO) {
  152 + List<IotDeviceDataDO> list = new ArrayList<>();
  153 + // 1. 获取设备信息
  154 + IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
  155 + // 2. 获取设备属性最新数据
  156 + List<IotThingModelDO> thingModelList = thingModelService.getProductThingModelListByProductKey(device.getProductKey());
  157 + thingModelList = filterList(thingModelList, thingModel -> IotThingModelTypeEnum.PROPERTY.getType()
  158 + .equals(thingModel.getType()));
  159 +
  160 + // 3. 过滤标识符和属性名称
  161 + if (deviceDataReqVO.getIdentifier() != null) {
  162 + thingModelList = filterList(thingModelList, thingModel -> thingModel.getIdentifier()
  163 + .toLowerCase().contains(deviceDataReqVO.getIdentifier().toLowerCase()));
  164 + }
  165 + if (deviceDataReqVO.getName() != null) {
  166 + thingModelList = filterList(thingModelList, thingModel -> thingModel.getName()
  167 + .toLowerCase().contains(deviceDataReqVO.getName().toLowerCase()));
  168 + }
  169 + // 4. 获取设备属性最新数据
  170 + thingModelList.forEach(thingModel -> {
  171 + IotDeviceDataDO deviceData = deviceDataRedisDAO.get(device.getProductKey(), device.getDeviceName(), thingModel.getIdentifier());
  172 + if (deviceData == null) {
  173 + deviceData = new IotDeviceDataDO();
  174 + deviceData.setProductKey(device.getProductKey());
  175 + deviceData.setDeviceName(device.getDeviceName());
  176 + deviceData.setIdentifier(thingModel.getIdentifier());
  177 + deviceData.setDeviceId(deviceDataReqVO.getDeviceId());
  178 + deviceData.setThingModelId(thingModel.getId());
  179 + deviceData.setName(thingModel.getName());
  180 + deviceData.setDataType(thingModel.getProperty().getDataType());
  181 + }
  182 + list.add(deviceData);
  183 + });
  184 + return list;
  185 + }
  186 +
  187 + @Override
  188 + public PageResult<Map<String, Object>> getHistoryDeviceProperties(IotDeviceDataPageReqVO deviceDataReqVO) {
  189 + PageResult<Map<String, Object>> pageResult = new PageResult<>();
  190 + // 1. 获取设备信息
  191 + IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
  192 + // 2. 获取设备属性历史数据
  193 + SelectVisualDO selectVisualDO = new SelectVisualDO();
  194 + selectVisualDO.setDataBaseName(getDatabaseName());
  195 + selectVisualDO.setTableName(getDeviceTableName(device.getProductKey(), device.getDeviceName()));
  196 + selectVisualDO.setFieldName(deviceDataReqVO.getIdentifier());
  197 + selectVisualDO.setStartTime(DateUtil.date(deviceDataReqVO.getTimes()[0].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()).getTime());
  198 + selectVisualDO.setEndTime(DateUtil.date(deviceDataReqVO.getTimes()[1].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()).getTime());
  199 + Map<String, Object> params = new HashMap<>();
  200 + params.put("rows", deviceDataReqVO.getPageSize());
  201 + params.put("page", (deviceDataReqVO.getPageNo() - 1) * deviceDataReqVO.getPageSize());
  202 + selectVisualDO.setParams(params);
  203 + pageResult.setList(tdEngineDMLMapper.selectHistoryDataList(selectVisualDO));
  204 + pageResult.setTotal(tdEngineDMLMapper.selectHistoryCount(selectVisualDO));
  205 + return pageResult;
  206 + }
  207 +
  208 + private String getDatabaseName() {
  209 + return StrUtil.subAfter(url, "/", true);
  210 + }
  211 +
  212 + private static String getDeviceTableName(String productKey, String deviceName) {
  213 + return String.format(IotConstants.DEVICE_TABLE_NAME_FORMAT, productKey, deviceName);
  214 + }
  215 +
  216 +}
0 217 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
... ... @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
10 10 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
11 11 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
  12 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 13 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
13 14 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
14 15 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
... ... @@ -60,9 +61,11 @@ public class IotDeviceServiceImpl implements IotDeviceService {
60 61 throw exception(PRODUCT_NOT_EXISTS);
61 62 }
62 63 // 1.2 校验设备标识是否唯一
63   - if (deviceMapper.selectByDeviceKey(createReqVO.getDeviceKey()) != null) {
64   - throw exception(DEVICE_KEY_EXISTS);
65   - }
  64 + TenantUtils.executeIgnore(() -> {
  65 + if (deviceMapper.selectByDeviceKey(createReqVO.getDeviceKey()) != null) {
  66 + throw exception(PRODUCT_KEY_EXISTS);
  67 + }
  68 + });
66 69 // 1.3 校验设备名称在同一产品下是否唯一
67 70 if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), createReqVO.getDeviceKey()) != null) {
68 71 throw exception(DEVICE_NAME_EXISTS);
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoService.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java
1   -package cn.iocoder.yudao.module.iot.service.plugininfo;
  1 +package cn.iocoder.yudao.module.iot.service.plugin;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
5   -import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
  4 +import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO;
  5 +import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO;
6 6 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
7 7 import jakarta.validation.Valid;
8 8 import org.springframework.web.multipart.MultipartFile;
9 9  
  10 +import java.util.List;
  11 +
10 12 /**
11 13 * IoT 插件信息 Service 接口
12 14 *
... ... @@ -58,7 +60,7 @@ public interface PluginInfoService {
58 60 * @param id 插件id
59 61 * @param file 文件
60 62 */
61   - void uploadJar(Long id, MultipartFile file);
  63 + void uploadFile(Long id, MultipartFile file);
62 64  
63 65 /**
64 66 * 更新插件的状态
... ... @@ -67,4 +69,11 @@ public interface PluginInfoService {
67 69 * @param status 状态
68 70 */
69 71 void updatePluginStatus(Long id, Integer status);
  72 +
  73 + /**
  74 + * 获得插件信息列表
  75 + *
  76 + * @return 插件信息列表
  77 + */
  78 + List<PluginInfoDO> getPluginInfoList();
70 79 }
71 80 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java
1   -package cn.iocoder.yudao.module.iot.service.plugininfo;
  1 +package cn.iocoder.yudao.module.iot.service.plugin;
2 2  
3   -import cn.hutool.core.io.IoUtil;
4 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6   -import cn.iocoder.yudao.module.infra.api.file.FileApi;
7   -import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
8   -import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
  5 +import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO;
9 7 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
10   -import cn.iocoder.yudao.module.iot.dal.mysql.plugininfo.PluginInfoMapper;
  8 +import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInfoMapper;
11 9 import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
12   -import jakarta.annotation.PostConstruct;
13 10 import jakarta.annotation.Resource;
14   -import lombok.SneakyThrows;
15 11 import lombok.extern.slf4j.Slf4j;
16 12 import org.pf4j.PluginDescriptor;
17 13 import org.pf4j.PluginState;
... ... @@ -22,11 +18,13 @@ import org.springframework.stereotype.Service;
22 18 import org.springframework.validation.annotation.Validated;
23 19 import org.springframework.web.multipart.MultipartFile;
24 20  
25   -import java.nio.file.Path;
  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;
26 26 import java.util.concurrent.Executors;
27 27 import java.util.concurrent.TimeUnit;
28   -import java.util.jar.JarEntry;
29   -import java.util.jar.JarFile;
30 28  
31 29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
32 30 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
... ... @@ -43,13 +41,9 @@ public class PluginInfoServiceImpl implements PluginInfoService {
43 41  
44 42 @Resource
45 43 private PluginInfoMapper pluginInfoMapper;
46   -
47 44 @Resource
48 45 private SpringPluginManager pluginManager;
49 46  
50   - @Resource
51   - private FileApi fileApi;
52   -
53 47 @Value("${pf4j.pluginsDir}")
54 48 private String pluginsDir;
55 49  
... ... @@ -82,7 +76,7 @@ public class PluginInfoServiceImpl implements PluginInfoService {
82 76 }
83 77  
84 78 // 卸载插件
85   - PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginId());
  79 + PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginKey());
86 80 if (plugin != null) {
87 81 // 查询插件是否是启动状态
88 82 if (plugin.getPluginState().equals(PluginState.STARTED)) {
... ... @@ -95,6 +89,19 @@ public class PluginInfoServiceImpl implements PluginInfoService {
95 89  
96 90 // 删除
97 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 + });
  104 +
98 105 }
99 106  
100 107 private PluginInfoDO validatePluginInfoExists(Long id) {
... ... @@ -116,73 +123,100 @@ public class PluginInfoServiceImpl implements PluginInfoService {
116 123 }
117 124  
118 125 @Override
119   - public void uploadJar(Long id, MultipartFile file) {
120   - // 1. 校验存在
  126 + public void uploadFile(Long id, MultipartFile file) {
  127 + // 1. 校验插件信息是否存在
121 128 PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
122 129  
123   - // 2. 判断文件名称与插件 ID 是否匹配
124   - String pluginId = pluginInfoDo.getPluginId();
  130 + // 2. 获取插件标识
  131 + String pluginKey = pluginInfoDo.getPluginKey();
  132 +
  133 + // 3. 停止并卸载旧的插件
  134 + stopAndUnloadPlugin(pluginKey);
  135 +
  136 + // 4. 上传新的插件文件
  137 + String pluginKeyNew = uploadAndLoadNewPlugin(file);
125 138  
126   - // 3. 停止卸载旧的插件
127   - // 3.1. 获取插件信息
128   - PluginWrapper plugin = pluginManager.getPlugin(pluginId);
  139 + // 5. 更新插件启用状态文件
  140 + updatePluginStatusFile(pluginKeyNew, false);
  141 +
  142 + // 6. 更新插件信息
  143 + updatePluginInfo(pluginInfoDo, pluginKeyNew, file);
  144 + }
  145 +
  146 + // 停止并卸载旧的插件
  147 + private void stopAndUnloadPlugin(String pluginKey) {
  148 + PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
129 149 if (plugin != null) {
130   - // 3.2. 如果插件状态是启动的,停止插件
131 150 if (plugin.getPluginState().equals(PluginState.STARTED)) {
132   - pluginManager.stopPlugin(pluginId);
  151 + pluginManager.stopPlugin(pluginKey); // 停止插件
133 152 }
134   - // 3.3. 卸载插件
135   - pluginManager.unloadPlugin(pluginId);
  153 + pluginManager.unloadPlugin(pluginKey); // 卸载插件
136 154 }
  155 + }
137 156  
138   - // 4. 上传插件
139   - String pluginIdNew;
  157 + // 上传并加载新的插件文件
  158 + private String uploadAndLoadNewPlugin(MultipartFile file) {
  159 + Path pluginsPath = Paths.get(pluginsDir);
140 160 try {
141   - String path = fileApi.createFile(pluginsDir, IoUtil.readBytes(file.getInputStream()));
142   - Path pluginPath = Path.of(path);
143   - pluginIdNew = pluginManager.loadPlugin(pluginPath);
  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 + }
144 172 } catch (Exception e) {
145 173 throw exception(PLUGIN_INSTALL_FAILED);
146 174 }
  175 + }
147 176  
148   - PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginIdNew);
149   - if (pluginWrapper == null) {
150   - throw exception(PLUGIN_INSTALL_FAILED);
151   - }
  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;
152 183  
153   - // 5. 读取配置文件和脚本
154   - String configJson = "";
155   - String script = "";
156   - try (JarFile jarFile = new JarFile(pluginWrapper.getPluginPath().toFile())) {
157   - // 5.1 获取config文件在jar包中的路径
158   - String configFile = "classes/config.json";
159   - JarEntry configEntry = jarFile.getJarEntry(configFile);
160   -
161   - if (configEntry != null) {
162   - // 5.2 读取配置文件
163   - configJson = IoUtil.readUtf8(jarFile.getInputStream(configEntry));
164   - log.info("configJson:{}", configJson);
  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);
165 199 }
166 200  
167   - // 5.3 读取script.js脚本
168   - String scriptFile = "classes/script.js";
169   - JarEntry scriptEntity = jarFile.getJarEntry(scriptFile);
170   - if (scriptEntity != null) {
171   - // 5.4 读取脚本文件
172   - script = IoUtil.readUtf8(jarFile.getInputStream(scriptEntity));
173   - log.info("script:{}", script);
  201 + if (oppositeLines.contains(pluginInfo)) {
  202 + oppositeLines.remove(pluginInfo);
  203 + Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE,
  204 + StandardOpenOption.TRUNCATE_EXISTING);
174 205 }
175   - } catch (Exception e) {
  206 + } catch (IOException e) {
176 207 throw exception(PLUGIN_INSTALL_FAILED);
177 208 }
  209 + }
178 210  
179   - pluginInfoDo.setPluginId(pluginIdNew);
  211 + // 更新插件信息
  212 + private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) {
  213 + pluginInfoDo.setPluginKey(pluginKeyNew);
180 214 pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus());
181   - pluginInfoDo.setFile(file.getOriginalFilename());
182   - pluginInfoDo.setConfigSchema(configJson);
183   - pluginInfoDo.setScript(script);
  215 + pluginInfoDo.setFileName(file.getOriginalFilename());
  216 + pluginInfoDo.setScript("");
184 217  
185   - PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
  218 + PluginDescriptor pluginDescriptor = pluginManager.getPlugin(pluginKeyNew).getDescriptor();
  219 + pluginInfoDo.setConfigSchema(pluginDescriptor.getPluginDescription());
186 220 pluginInfoDo.setVersion(pluginDescriptor.getVersion());
187 221 pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription());
188 222 pluginInfoMapper.updateById(pluginInfoDo);
... ... @@ -190,52 +224,47 @@ public class PluginInfoServiceImpl implements PluginInfoService {
190 224  
191 225 @Override
192 226 public void updatePluginStatus(Long id, Integer status) {
193   - // 1. 校验存在
  227 + // 1. 校验插件信息是否存在
194 228 PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
195 229  
196   - // 插件状态无效
  230 + // 2. 校验插件状态是否有效
197 231 if (!IotPluginStatusEnum.contains(status)) {
198 232 throw exception(PLUGIN_STATUS_INVALID);
199 233 }
200 234  
201   - String pluginId = pluginInfoDo.getPluginId();
202   - PluginWrapper plugin = pluginManager.getPlugin(pluginId);
  235 + // 3. 获取插件标识和插件实例
  236 + String pluginKey = pluginInfoDo.getPluginKey();
  237 + PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
  238 +
  239 + // 4. 根据状态更新插件
203 240 if (plugin != null) {
204   - if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) && plugin.getPluginState() != PluginState.STARTED) {
205   - // 启动插件
206   - pluginManager.startPlugin(pluginId);
207   - } else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) && plugin.getPluginState() == PluginState.STARTED) {
208   - // 停止插件
209   - pluginManager.stopPlugin(pluginId);
  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); // 更新插件状态文件为禁用
210 252 }
211 253 } else {
212   - // 已经停止,未获取到插件
  254 + // 5. 插件不存在且状态为停止,抛出异常
213 255 if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
214 256 throw exception(PLUGIN_STATUS_INVALID);
215 257 }
216 258 }
  259 +
  260 + // 6. 更新数据库中的插件状态
217 261 pluginInfoDo.setStatus(status);
218 262 pluginInfoMapper.updateById(pluginInfoDo);
219 263 }
220 264  
221   -// @PostConstruct
222   -// public void init() {
223   -// Executors.newSingleThreadScheduledExecutor().schedule(this::startPlugins, 3, TimeUnit.SECONDS);
224   -// }
225   -//
226   -// @SneakyThrows
227   -// private void startPlugins() {
228   -// for (PluginInfoDO pluginInfoDO : pluginInfoMapper.selectList()) {
229   -// if (!IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
230   -// continue;
231   -// }
232   -// log.info("start plugin:{}", pluginInfoDO.getPluginId());
233   -// try {
234   -// pluginManager.startPlugin(pluginInfoDO.getPluginId());
235   -// } catch (Exception e) {
236   -// log.error("start plugin error", e);
237   -// }
238   -// }
239   -// }
  265 + @Override
  266 + public List<PluginInfoDO> getPluginInfoList() {
  267 + return pluginInfoMapper.selectList(null);
  268 + }
240 269  
241 270 }
242 271 \ 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 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.plugin;
  2 +
  3 +/**
  4 + * IoT 插件实例 Service 接口
  5 + *
  6 + * @author 芋道源码
  7 + */
  8 +public interface PluginInstanceService {
  9 +
  10 + /**
  11 + * 更新IoT 插件实例
  12 + */
  13 + void updatePluginInstances();
  14 +
  15 +}
0 16 \ 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 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.plugin;
  2 +
  3 +import cn.hutool.core.net.NetUtil;
  4 +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.plugininstance.PluginInstanceDO;
  7 +import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInstanceMapper;
  8 +import jakarta.annotation.Resource;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.pf4j.PluginWrapper;
  11 +import org.pf4j.spring.SpringPluginManager;
  12 +import org.springframework.beans.factory.annotation.Value;
  13 +import org.springframework.stereotype.Service;
  14 +import org.springframework.validation.annotation.Validated;
  15 +
  16 +import java.util.List;
  17 +
  18 +/**
  19 + * IoT 插件实例 Service 实现类
  20 + *
  21 + * @author 芋道源码
  22 + */
  23 +@Service
  24 +@Validated
  25 +@Slf4j
  26 +public class PluginInstanceServiceImpl implements PluginInstanceService {
  27 +
  28 + /**
  29 + * 主程序id
  30 + */
  31 + public static final String MAIN_ID = IdUtil.fastSimpleUUID();
  32 +
  33 + @Resource
  34 + private PluginInfoService pluginInfoService;
  35 + @Resource
  36 + private PluginInstanceMapper pluginInstanceMapper;
  37 + @Resource
  38 + private SpringPluginManager pluginManager;
  39 +
  40 + @Value("${server.port:48080}")
  41 + private int port;
  42 +
  43 +
  44 + @Override
  45 + public void updatePluginInstances() {
  46 + // 1. 查询 pf4j 插件列表
  47 + List<PluginWrapper> plugins = pluginManager.getPlugins();
  48 +
  49 + // 2. 查询插件信息列表
  50 + List<PluginInfoDO> pluginInfos = pluginInfoService.getPluginInfoList();
  51 +
  52 + // 动态获取主程序的 IP 和端口
  53 + String mainIp = getLocalIpAddress();
  54 +
  55 + // 3. 遍历插件列表,并保存为插件实例
  56 + for (PluginWrapper plugin : plugins) {
  57 + String pluginKey = plugin.getPluginId();
  58 + PluginInfoDO pluginInfo = pluginInfos.stream()
  59 + .filter(pluginInfoDO -> pluginInfoDO.getPluginKey().equals(pluginKey))
  60 + .findFirst()
  61 + .orElse(null);
  62 +
  63 + // 4. 如果插件信息不存在,则跳过
  64 + if (pluginInfo == null) {
  65 + continue;
  66 + }
  67 +
  68 + // 5. 查询插件实例
  69 + PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(MAIN_ID, pluginInfo.getId());
  70 +
  71 + // 6. 如果插件实例不存在,则创建
  72 + 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());
  79 + pluginInstanceMapper.insert(pluginInstance);
  80 + } else {
  81 + // 7. 如果插件实例存在,则更新
  82 + pluginInstance.setHeartbeatAt(System.currentTimeMillis());
  83 + pluginInstanceMapper.updateById(pluginInstance);
  84 + }
  85 + }
  86 + }
  87 +
  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 +}
0 101 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceService.java deleted
1   -package cn.iocoder.yudao.module.iot.service.plugininstance;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstancePageReqVO;
5   -import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstanceSaveReqVO;
6   -import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
7   -import jakarta.validation.Valid;
8   -
9   -/**
10   - * IoT 插件实例 Service 接口
11   - *
12   - * @author 芋道源码
13   - */
14   -public interface PluginInstanceService {
15   -
16   - /**
17   - * 创建IoT 插件实例
18   - *
19   - * @param createReqVO 创建信息
20   - * @return 编号
21   - */
22   - Long createPluginInstance(@Valid PluginInstanceSaveReqVO createReqVO);
23   -
24   - /**
25   - * 更新IoT 插件实例
26   - *
27   - * @param updateReqVO 更新信息
28   - */
29   - void updatePluginInstance(@Valid PluginInstanceSaveReqVO updateReqVO);
30   -
31   - /**
32   - * 删除IoT 插件实例
33   - *
34   - * @param id 编号
35   - */
36   - void deletePluginInstance(Long id);
37   -
38   - /**
39   - * 获得IoT 插件实例
40   - *
41   - * @param id 编号
42   - * @return IoT 插件实例
43   - */
44   - PluginInstanceDO getPluginInstance(Long id);
45   -
46   - /**
47   - * 获得IoT 插件实例分页
48   - *
49   - * @param pageReqVO 分页查询
50   - * @return IoT 插件实例分页
51   - */
52   - PageResult<PluginInstanceDO> getPluginInstancePage(PluginInstancePageReqVO pageReqVO);
53   -
54   -}
55 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.iot.service.plugininstance;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5   -import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstancePageReqVO;
6   -import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstanceSaveReqVO;
7   -import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
8   -import cn.iocoder.yudao.module.iot.dal.mysql.plugininstance.PluginInstanceMapper;
9   -import jakarta.annotation.Resource;
10   -import org.springframework.stereotype.Service;
11   -import org.springframework.validation.annotation.Validated;
12   -
13   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
14   -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INSTANCE_NOT_EXISTS;
15   -
16   -/**
17   - * IoT 插件实例 Service 实现类
18   - *
19   - * @author 芋道源码
20   - */
21   -@Service
22   -@Validated
23   -public class PluginInstanceServiceImpl implements PluginInstanceService {
24   -
25   - @Resource
26   - private PluginInstanceMapper pluginInstanceMapper;
27   -
28   - @Override
29   - public Long createPluginInstance(PluginInstanceSaveReqVO createReqVO) {
30   - // 插入
31   - PluginInstanceDO pluginInstance = BeanUtils.toBean(createReqVO, PluginInstanceDO.class);
32   - pluginInstanceMapper.insert(pluginInstance);
33   - // 返回
34   - return pluginInstance.getId();
35   - }
36   -
37   - @Override
38   - public void updatePluginInstance(PluginInstanceSaveReqVO updateReqVO) {
39   - // 校验存在
40   - validatePluginInstanceExists(updateReqVO.getId());
41   - // 更新
42   - PluginInstanceDO updateObj = BeanUtils.toBean(updateReqVO, PluginInstanceDO.class);
43   - pluginInstanceMapper.updateById(updateObj);
44   - }
45   -
46   - @Override
47   - public void deletePluginInstance(Long id) {
48   - // 校验存在
49   - validatePluginInstanceExists(id);
50   - // 删除
51   - pluginInstanceMapper.deleteById(id);
52   - }
53   -
54   - private void validatePluginInstanceExists(Long id) {
55   - if (pluginInstanceMapper.selectById(id) == null) {
56   - throw exception(PLUGIN_INSTANCE_NOT_EXISTS);
57   - }
58   - }
59   -
60   - @Override
61   - public PluginInstanceDO getPluginInstance(Long id) {
62   - return pluginInstanceMapper.selectById(id);
63   - }
64   -
65   - @Override
66   - public PageResult<PluginInstanceDO> getPluginInstancePage(PluginInstancePageReqVO pageReqVO) {
67   - return pluginInstanceMapper.selectPage(pageReqVO);
68   - }
69   -
70   -}
71 0 \ 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
... ... @@ -2,13 +2,14 @@ package cn.iocoder.yudao.module.iot.service.product;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
5 6 import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO;
6 7 import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductSaveReqVO;
7 8 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
8 9 import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper;
9 10 import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
  11 +import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
10 12 import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
11   -import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
12 13 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
13 14 import jakarta.annotation.Resource;
14 15 import org.springframework.context.annotation.Lazy;
... ... @@ -34,18 +35,22 @@ public class IotProductServiceImpl implements IotProductService {
34 35 private IotProductMapper productMapper;
35 36  
36 37 @Resource
37   - @Lazy // 延迟加载,解决循环依赖
38   - private IotProductThingModelService thingModelFunctionService;
39   - @Resource
40 38 @Lazy // 延迟加载,解决循环依赖
41 39 private IotThingModelMessageService thingModelMessageService;
  40 + @Resource
  41 + @Lazy // 延迟加载,解决循环依赖
  42 + private IotDevicePropertyDataService devicePropertyDataService;
42 43  
43 44 @Override
44 45 public Long createProduct(IotProductSaveReqVO createReqVO) {
45   - // 1. 生成 ProductKey
46   - if (productMapper.selectByProductKey(createReqVO.getProductKey()) != null) {
47   - throw exception(PRODUCT_KEY_EXISTS);
48   - }
  46 + // 1. 校验 ProductKey
  47 + TenantUtils.executeIgnore(() -> {
  48 + // 为什么忽略租户?避免多个租户之间,productKey 重复,导致 TDengine 设备属性表重复
  49 + if (productMapper.selectByProductKey(createReqVO.getProductKey()) != null) {
  50 + throw exception(PRODUCT_KEY_EXISTS);
  51 + }
  52 + });
  53 +
49 54 // 2. 插入
50 55 IotProductDO product = BeanUtils.toBean(createReqVO, IotProductDO.class)
51 56 .setStatus(IotProductStatusEnum.UNPUBLISHED.getStatus());
... ... @@ -119,8 +124,8 @@ public class IotProductServiceImpl implements IotProductService {
119 124 // 3. 产品是发布状态
120 125 if (Objects.equals(status, IotProductStatusEnum.PUBLISHED.getStatus())) {
121 126 // 3.1 创建产品超级表数据模型
122   - thingModelFunctionService.createSuperTableDataModel(id);
123   - // 3.2 创建物模型日志超级表数据模型
  127 + devicePropertyDataService.defineDevicePropertyData(id);
  128 + // 3.2 创建物模型日志超级表数据模型 TODO 待定:message 要不要分;
124 129 thingModelMessageService.createSuperTable(id);
125 130 }
126 131 productMapper.updateById(updateObj);
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotSuperTableService.java deleted
1   -package cn.iocoder.yudao.module.iot.service.tdengine;
2   -
3   -
4   -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
5   -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
6   -
7   -import java.util.List;
8   -
9   -/**
10   - * IoT 超级表服务,负责根据物模型创建和更新超级表,以及创建超级表的子表等操作。
11   - */
12   -public interface IotSuperTableService {
13   -
14   - /**
15   - * 创建超级表数据模型
16   - */
17   - void createSuperTableDataModel(IotProductDO product, List<IotProductThingModelDO> thingModelList);
18   -
19   -}
20 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotSuperTableServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.iot.service.tdengine;
2   -
3   -import cn.hutool.core.bean.BeanUtil;
4   -import cn.hutool.core.collection.CollUtil;
5   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
6   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelRespVO;
7   -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
8   -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
9   -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
10   -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
11   -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
12   -import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper;
13   -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
14   -import jakarta.annotation.Resource;
15   -import lombok.extern.slf4j.Slf4j;
16   -import org.springframework.beans.factory.annotation.Value;
17   -import org.springframework.stereotype.Service;
18   -
19   -import java.util.*;
20   -import java.util.stream.Collectors;
21   -
22   -/**
23   - * IoT 超级表服务实现类,负责根据物模型创建和更新超级表,以及创建超级表的子表等操作。
24   - */
25   -@Service
26   -@Slf4j
27   -public class IotSuperTableServiceImpl implements IotSuperTableService {
28   -
29   - @Resource
30   - private TdEngineDDLMapper tdEngineDDLMapper;
31   -
32   - @Value("${spring.datasource.dynamic.datasource.tdengine.url}")
33   - private String url;
34   -
35   - @Override
36   - public void createSuperTableDataModel(IotProductDO product, List<IotProductThingModelDO> thingModelList) {
37   - ThingModelRespVO thingModel = buildThingModel(product, thingModelList);
38   -
39   - if (thingModel.getModel() == null || CollUtil.isEmpty(thingModel.getModel().getProperties())) {
40   - log.warn("物模型属性列表为空,不创建超级表");
41   - return;
42   - }
43   -
44   - String superTableName = getSuperTableName(product.getDeviceType(), product.getProductKey());
45   - String databaseName = getDatabaseName();
46   -
47   - List<Map<String, Object>> results = tdEngineDDLMapper.showSuperTables(new TdTableDO(databaseName, superTableName));
48   - int tableExists = results == null || results.isEmpty() ? 0 : results.size();
49   - if (tableExists > 0) {
50   - updateSuperTable(thingModel, product.getDeviceType());
51   - } else {
52   - createSuperTable(thingModel, product.getDeviceType());
53   - }
54   - }
55   -
56   - /**
57   - * 创建超级表
58   - */
59   - private void createSuperTable(ThingModelRespVO thingModel, Integer deviceType) {
60   - // 解析物模型,获取字段列表
61   - List<TdFieldDO> schemaFields = new ArrayList<>();
62   - schemaFields.add(TdFieldDO.builder()
63   - .fieldName("time")
64   - .dataType("TIMESTAMP")
65   - .build());
66   - schemaFields.addAll(FieldParser.parse(thingModel));
67   -
68   - // 设置超级表的标签
69   - List<TdFieldDO> tagsFields = List.of(
70   - TdFieldDO.builder().fieldName("product_key").dataType("NCHAR").dataLength(64).build(),
71   - TdFieldDO.builder().fieldName("device_key").dataType("NCHAR").dataLength(64).build(),
72   - TdFieldDO.builder().fieldName("device_name").dataType("NCHAR").dataLength(64).build(),
73   - TdFieldDO.builder().fieldName("device_type").dataType("INT").build()
74   - );
75   -
76   - // 获取超级表的名称和数据库名称
77   - String superTableName = getSuperTableName(deviceType, thingModel.getProductKey());
78   - String databaseName = getDatabaseName();
79   -
80   - // 创建超级表
81   - tdEngineDDLMapper.createSuperTable(new TdTableDO(databaseName, superTableName, schemaFields, tagsFields));
82   - }
83   -
84   - /**
85   - * 更新超级表
86   - */
87   - private void updateSuperTable(ThingModelRespVO thingModel, Integer deviceType) {
88   - String superTableName = getSuperTableName(deviceType, thingModel.getProductKey());
89   - try {
90   - List<TdFieldDO> oldFields = getTableFields(superTableName);
91   - List<TdFieldDO> newFields = FieldParser.parse(thingModel);
92   -
93   - updateTableFields(superTableName, oldFields, newFields);
94   - } catch (Exception e) {
95   - log.error("更新物模型超级表失败: {}", e.getMessage(), e);
96   - }
97   - }
98   -
99   - /**
100   - * 获取表的字段信息
101   - */
102   - private List<TdFieldDO> getTableFields(String tableName) {
103   - List<Map<String, Object>> tableDescription = tdEngineDDLMapper.describeSuperTable(new TdTableDO(getDatabaseName(), tableName));
104   - if (CollUtil.isEmpty(tableDescription)) {
105   - return Collections.emptyList();
106   - }
107   -
108   - return tableDescription.stream()
109   - .filter(map -> !"TAG".equals(map.get("note")))
110   - .filter(map -> !"time".equals(map.get("field")))
111   - .map(map -> TdFieldDO.builder()
112   - .fieldName((String) map.get("field"))
113   - .dataType((String) map.get("type"))
114   - .dataLength((Integer) map.get("length"))
115   - .build())
116   - .collect(Collectors.toList());
117   - }
118   -
119   - /**
120   - * 更新表的字段,包括新增、修改和删除字段
121   - */
122   - private void updateTableFields(String tableName, List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
123   - String databaseName = getDatabaseName();
124   -
125   - // 获取新增、修改、删除的字段
126   - List<TdFieldDO> addFields = getAddFields(oldFields, newFields);
127   - List<TdFieldDO> modifyFields = getModifyFields(oldFields, newFields);
128   - List<TdFieldDO> dropFields = getDropFields(oldFields, newFields);
129   -
130   - // 添加新增字段
131   - if (CollUtil.isNotEmpty(addFields)) {
132   - for (TdFieldDO addField : addFields) {
133   - tdEngineDDLMapper.addColumnForSuperTable(TdTableDO.builder()
134   - .dataBaseName(databaseName)
135   - .superTableName(tableName)
136   - .column(addField)
137   - .build());
138   - }
139   - }
140   - // 删除旧字段
141   - if (CollUtil.isNotEmpty(dropFields)) {
142   - for (TdFieldDO dropField : dropFields) {
143   - tdEngineDDLMapper.dropColumnForSuperTable(TdTableDO.builder()
144   - .dataBaseName(databaseName)
145   - .superTableName(tableName)
146   - .column(dropField)
147   - .build());
148   - }
149   - }
150   - // 修改字段(先删除再添加)
151   - if (CollUtil.isNotEmpty(modifyFields)) {
152   - for (TdFieldDO modifyField : modifyFields) {
153   - tdEngineDDLMapper.dropColumnForSuperTable(TdTableDO.builder()
154   - .dataBaseName(databaseName)
155   - .superTableName(tableName)
156   - .column(modifyField)
157   - .build());
158   - tdEngineDDLMapper.addColumnForSuperTable(TdTableDO.builder()
159   - .dataBaseName(databaseName)
160   - .superTableName(tableName)
161   - .column(modifyField)
162   - .build());
163   - }
164   - }
165   - }
166   -
167   - /**
168   - * 获取需要新增的字段
169   - */
170   - private List<TdFieldDO> getAddFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
171   - Set<String> oldFieldNames = oldFields.stream()
172   - .map(TdFieldDO::getFieldName)
173   - .collect(Collectors.toSet());
174   - return newFields.stream()
175   - .filter(f -> !oldFieldNames.contains(f.getFieldName()))
176   - .collect(Collectors.toList());
177   - }
178   -
179   - /**
180   - * 获取需要修改的字段
181   - */
182   - private List<TdFieldDO> getModifyFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
183   - Map<String, TdFieldDO> oldFieldMap = oldFields.stream()
184   - .collect(Collectors.toMap(TdFieldDO::getFieldName, f -> f));
185   -
186   - return newFields.stream()
187   - .filter(f -> {
188   - TdFieldDO oldField = oldFieldMap.get(f.getFieldName());
189   - return oldField != null && (
190   - !oldField.getDataType().equals(f.getDataType()) ||
191   - !Objects.equals(oldField.getDataLength(), f.getDataLength())
192   - );
193   - })
194   - .collect(Collectors.toList());
195   - }
196   -
197   - /**
198   - * 获取需要删除的字段
199   - */
200   - private List<TdFieldDO> getDropFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
201   - Set<String> newFieldNames = newFields.stream()
202   - .map(TdFieldDO::getFieldName)
203   - .collect(Collectors.toSet());
204   - return oldFields.stream()
205   - .filter(f -> !"time".equals(f.getFieldName()))
206   - .filter(f -> !newFieldNames.contains(f.getFieldName()))
207   - .collect(Collectors.toList());
208   - }
209   -
210   - /**
211   - * 构建物模型
212   - */
213   - private ThingModelRespVO buildThingModel(IotProductDO product, List<IotProductThingModelDO> thingModelList) {
214   - ThingModelRespVO thingModel = new ThingModelRespVO();
215   - thingModel.setId(product.getId());
216   - thingModel.setProductKey(product.getProductKey());
217   -
218   - List<ThingModelProperty> properties = thingModelList.stream()
219   - .filter(item -> IotProductThingModelTypeEnum.PROPERTY.equals(
220   - IotProductThingModelTypeEnum.valueOfType(item.getType())))
221   - .map(this::buildThingModelProperty)
222   - .collect(Collectors.toList());
223   -
224   - ThingModelRespVO.Model model = new ThingModelRespVO.Model();
225   - model.setProperties(properties);
226   - thingModel.setModel(model);
227   -
228   - return thingModel;
229   - }
230   -
231   - /**
232   - * 构建物模型属性
233   - */
234   - private ThingModelProperty buildThingModelProperty(IotProductThingModelDO thingModel) {
235   - ThingModelProperty property = BeanUtil.copyProperties(thingModel, ThingModelProperty.class);
236   - property.setDataType(thingModel.getProperty().getDataType());
237   - return property;
238   - }
239   -
240   - /**
241   - * 获取数据库名称
242   - */
243   - private String getDatabaseName() {
244   - int index = url.lastIndexOf("/");
245   - return index != -1 ? url.substring(index + 1) : url;
246   - }
247   -
248   - /**
249   - * 获取超级表名称
250   - */
251   - private String getSuperTableName(Integer deviceType, String productKey) {
252   - String prefix = switch (deviceType) {
253   - case 1 -> "gateway_sub_";
254   - case 2 -> "gateway_";
255   - default -> "device_";
256   - };
257   - return (prefix + productKey).toLowerCase();
258   - }
259   -
260   -}
261 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java
... ... @@ -11,16 +11,16 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
11 11 import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
12 12 import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
13 13 import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
14   -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
  14 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
15 15 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
16 16 import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
17 17 import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper;
18 18 import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
19 19 import cn.iocoder.yudao.module.iot.enums.IotConstants;
20 20 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
21   -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
  21 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
22 22 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
23   -import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
  23 +import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
24 24 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
25 25 import cn.iocoder.yudao.module.iot.util.IotTdDatabaseUtils;
26 26 import jakarta.annotation.Resource;
... ... @@ -52,7 +52,7 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
52 52 private String url;
53 53  
54 54 @Resource
55   - private IotProductThingModelService iotProductThingModelService;
  55 + private IotThingModelService iotThingModelService;
56 56 @Resource
57 57 private IotDeviceService iotDeviceService;
58 58 @Resource
... ... @@ -83,7 +83,7 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
83 83  
84 84 // 2. 获取设备属性并进行物模型校验,过滤非物模型属性
85 85 Map<String, Object> params = thingModelMessage.dataToMap();
86   - List<IotProductThingModelDO> thingModelList = getValidThingModelList(thingModelMessage.getProductKey());
  86 + List<IotThingModelDO> thingModelList = getValidThingModelList(thingModelMessage.getProductKey());
87 87 if (thingModelList.isEmpty()) {
88 88 return;
89 89 }
... ... @@ -102,9 +102,9 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
102 102 .build());
103 103 }
104 104  
105   - private List<IotProductThingModelDO> getValidThingModelList(String productKey) {
106   - return filterList(iotProductThingModelService.getProductThingModelListByProductKey(productKey),
107   - thingModel -> IotProductThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
  105 + private List<IotThingModelDO> getValidThingModelList(String productKey) {
  106 + return filterList(iotThingModelService.getProductThingModelListByProductKey(productKey),
  107 + thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
108 108 }
109 109  
110 110 @Override
... ... @@ -134,21 +134,17 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
134 134 tdEngineDDLMapper.createSuperTable(new TdTableDO(databaseName, superTableName, schemaFields, tagsFields));
135 135 }
136 136  
137   - private List<IotProductThingModelDO> getValidFunctionList(String productKey) {
138   - // TODO @puhui999:使用 convertList 会好点哈
139   - return iotProductThingModelService
140   - .getProductThingModelListByProductKey(productKey)
141   - .stream()
142   - .filter(function -> IotProductThingModelTypeEnum.PROPERTY.getType().equals(function.getType()))
143   - .toList();
  137 + private List<IotThingModelDO> getValidFunctionList(String productKey) {
  138 + return filterList(iotThingModelService.getProductThingModelListByProductKey(productKey),
  139 + thingModel -> IotThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
144 140 }
145 141  
146   - private List<TdFieldDO> filterAndCollectValidFields(Map<String, Object> params, List<IotProductThingModelDO> thingModelList, IotDeviceDO device, Long time) {
  142 + private List<TdFieldDO> filterAndCollectValidFields(Map<String, Object> params, List<IotThingModelDO> thingModelList, IotDeviceDO device, Long time) {
147 143 // 1. 获取属性标识符集合
148   - Set<String> propertyIdentifiers = convertSet(thingModelList, IotProductThingModelDO::getIdentifier);
  144 + Set<String> propertyIdentifiers = convertSet(thingModelList, IotThingModelDO::getIdentifier);
149 145  
150 146 // 2. 构建属性标识符和属性的映射
151   - Map<String, IotProductThingModelDO> thingModelMap = convertMap(thingModelList, IotProductThingModelDO::getIdentifier);
  147 + Map<String, IotThingModelDO> thingModelMap = convertMap(thingModelList, IotThingModelDO::getIdentifier);
152 148  
153 149 // 3. 过滤并收集有效的属性字段
154 150 List<TdFieldDO> schemaFieldValues = new ArrayList<>();
... ... @@ -168,21 +164,21 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
168 164 * 缓存设备属性
169 165 *
170 166 * @param device 设备信息
171   - * @param iotProductThingModelDO 物模型属性
  167 + * @param iotThingModelDO 物模型属性
172 168 * @param val 属性值
173 169 * @param time 时间
174 170 */
175   - private void setDeviceDataCache(IotDeviceDO device, IotProductThingModelDO iotProductThingModelDO, Object val, Long time) {
  171 + private void setDeviceDataCache(IotDeviceDO device, IotThingModelDO iotThingModelDO, Object val, Long time) {
176 172 IotDeviceDataDO deviceData = IotDeviceDataDO.builder()
177 173 .productKey(device.getProductKey())
178 174 .deviceName(device.getDeviceName())
179   - .identifier(iotProductThingModelDO.getIdentifier())
  175 + .identifier(iotThingModelDO.getIdentifier())
180 176 .value(val != null ? val.toString() : null)
181 177 .updateTime(DateUtil.toLocalDateTime(new Date(time)))
182 178 .deviceId(device.getId())
183   - .thingModelId(iotProductThingModelDO.getId())
184   - .name(iotProductThingModelDO.getName())
185   - .dataType(iotProductThingModelDO.getProperty().getDataType())
  179 + .thingModelId(iotThingModelDO.getId())
  180 + .name(iotThingModelDO.getName())
  181 + .dataType(iotThingModelDO.getProperty().getDataType())
186 182 .build();
187 183 deviceDataRedisDAO.set(deviceData);
188 184 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotProductThingModelServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.iot.service.thingmodel;
2   -
3   -import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.util.ObjectUtil;
5   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
6   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7   -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
8   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent;
9   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
10   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
11   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelArgument;
12   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelArrayDataSpecs;
13   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;
14   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelPageReqVO;
15   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelSaveReqVO;
16   -import cn.iocoder.yudao.module.iot.convert.thingmodel.IotProductThingModelConvert;
17   -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
18   -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
19   -import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotProductThingModelMapper;
20   -import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
21   -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelAccessModeEnum;
22   -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
23   -import cn.iocoder.yudao.module.iot.service.product.IotProductService;
24   -import cn.iocoder.yudao.module.iot.service.tdengine.IotSuperTableService;
25   -import jakarta.annotation.Resource;
26   -import lombok.extern.slf4j.Slf4j;
27   -import org.springframework.stereotype.Service;
28   -import org.springframework.transaction.annotation.Transactional;
29   -import org.springframework.validation.annotation.Validated;
30   -
31   -import java.util.*;
32   -import java.util.stream.Collectors;
33   -
34   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
35   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList;
36   -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
37   -
38   -/**
39   - * IoT 产品物模型 Service 实现类
40   - *
41   - * @author 芋道源码
42   - */
43   -@Service
44   -@Validated
45   -@Slf4j
46   -public class IotProductThingModelServiceImpl implements IotProductThingModelService {
47   -
48   - @Resource
49   - private IotProductThingModelMapper productThingModelMapper;
50   -
51   - @Resource
52   - private IotProductService productService;
53   - @Resource
54   - private IotSuperTableService dbStructureDataService;
55   -
56   - @Override
57   - @Transactional(rollbackFor = Exception.class)
58   - public Long createProductThingModel(IotProductThingModelSaveReqVO createReqVO) {
59   - // 1. 校验功能标识符在同一产品下是否唯一
60   - validateIdentifierUnique(createReqVO.getProductId(), createReqVO.getIdentifier());
61   -
62   - // 2. 功能名称在同一产品下是否唯一
63   - validateNameUnique(createReqVO.getProductId(), createReqVO.getName());
64   -
65   - // 3. 系统保留字段,不能用于标识符定义
66   - validateNotDefaultEventAndService(createReqVO.getIdentifier());
67   -
68   - // 4. 校验产品状态,发布状态下,不允许新增功能
69   - validateProductStatus(createReqVO.getProductId());
70   -
71   - // 5. 插入数据库
72   - IotProductThingModelDO thingModel = IotProductThingModelConvert.INSTANCE.convert(createReqVO);
73   - productThingModelMapper.insert(thingModel);
74   -
75   - // 6. 如果创建的是属性,需要更新默认的事件和服务
76   - if (Objects.equals(createReqVO.getType(), IotProductThingModelTypeEnum.PROPERTY.getType())) {
77   - createDefaultEventsAndServices(createReqVO.getProductId(), createReqVO.getProductKey());
78   - }
79   - return thingModel.getId();
80   - }
81   -
82   - private void validateProductStatus(Long createReqVO) {
83   - IotProductDO product = productService.getProduct(createReqVO);
84   - if (Objects.equals(product.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
85   - throw exception(PRODUCT_STATUS_NOT_ALLOW_THING_MODEL);
86   - }
87   - }
88   -
89   - private void validateNotDefaultEventAndService(String identifier) {
90   - // set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义
91   - if (CollUtil.containsAny(Arrays.asList("set", "get", "post", "property", "event", "time", "value"), Collections.singletonList(identifier))) {
92   - throw exception(THING_MODEL_IDENTIFIER_INVALID);
93   - }
94   - }
95   -
96   - private void validateNameUnique(Long productId, String name) {
97   - IotProductThingModelDO thingModel = productThingModelMapper.selectByProductIdAndName(productId, name);
98   - if (thingModel != null) {
99   - throw exception(THING_MODEL_NAME_EXISTS);
100   - }
101   - }
102   -
103   - private void validateIdentifierUnique(Long productId, String identifier) {
104   - IotProductThingModelDO thingModel = productThingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
105   - if (thingModel != null) {
106   - throw exception(THING_MODEL_IDENTIFIER_EXISTS);
107   - }
108   - }
109   -
110   - @Override
111   - @Transactional(rollbackFor = Exception.class)
112   - public void updateProductThingModel(IotProductThingModelSaveReqVO updateReqVO) {
113   - // 1. 校验功能是否存在
114   - validateProductThingModelMapperExists(updateReqVO.getId());
115   -
116   - // 2. 校验功能标识符是否唯一
117   - validateIdentifierUniqueForUpdate(updateReqVO.getId(), updateReqVO.getProductId(), updateReqVO.getIdentifier());
118   -
119   - // 3. 校验产品状态,发布状态下,不允许操作功能
120   - validateProductStatus(updateReqVO.getProductId());
121   -
122   - // 4. 更新数据库
123   - IotProductThingModelDO thingModel = IotProductThingModelConvert.INSTANCE.convert(updateReqVO);
124   - productThingModelMapper.updateById(thingModel);
125   -
126   - // 5. 如果更新的是属性,需要更新默认的事件和服务
127   - if (Objects.equals(updateReqVO.getType(), IotProductThingModelTypeEnum.PROPERTY.getType())) {
128   - createDefaultEventsAndServices(updateReqVO.getProductId(), updateReqVO.getProductKey());
129   - }
130   - }
131   -
132   - private void validateIdentifierUniqueForUpdate(Long id, Long productId, String identifier) {
133   - IotProductThingModelDO thingModel = productThingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
134   - if (thingModel != null && ObjectUtil.notEqual(thingModel.getId(), id)) {
135   - throw exception(THING_MODEL_IDENTIFIER_EXISTS);
136   - }
137   - }
138   -
139   - @Override
140   - @Transactional(rollbackFor = Exception.class)
141   - public void deleteProductThingModel(Long id) {
142   - // 1. 校验功能是否存在
143   - IotProductThingModelDO thingModel = productThingModelMapper.selectById(id);
144   - if (thingModel == null) {
145   - throw exception(THING_MODEL_NOT_EXISTS);
146   - }
147   -
148   - // 3. 校验产品状态,发布状态下,不允许操作功能
149   - validateProductStatus(thingModel.getProductId());
150   -
151   - // 2. 删除功能
152   - productThingModelMapper.deleteById(id);
153   -
154   - // 3. 如果删除的是属性,需要更新默认的事件和服务
155   - if (Objects.equals(thingModel.getType(), IotProductThingModelTypeEnum.PROPERTY.getType())) {
156   - createDefaultEventsAndServices(thingModel.getProductId(), thingModel.getProductKey());
157   - }
158   - }
159   -
160   - /**
161   - * 校验功能是否存在
162   - *
163   - * @param id 功能编号
164   - */
165   - private void validateProductThingModelMapperExists(Long id) {
166   - if (productThingModelMapper.selectById(id) == null) {
167   - throw exception(THING_MODEL_NOT_EXISTS);
168   - }
169   - }
170   -
171   - @Override
172   - public IotProductThingModelDO getProductThingModel(Long id) {
173   - return productThingModelMapper.selectById(id);
174   - }
175   -
176   - @Override
177   - public List<IotProductThingModelDO> getProductThingModelListByProductId(Long productId) {
178   - return productThingModelMapper.selectListByProductId(productId);
179   - }
180   -
181   - @Override
182   - public PageResult<IotProductThingModelDO> getProductThingModelPage(IotProductThingModelPageReqVO pageReqVO) {
183   - return productThingModelMapper.selectPage(pageReqVO);
184   - }
185   -
186   - @Override
187   - public void createSuperTableDataModel(Long productId) {
188   - // 1. 查询产品
189   - IotProductDO product = productService.getProduct(productId);
190   -
191   - // 2. 查询产品的物模型功能列表
192   - List<IotProductThingModelDO> thingModelList = productThingModelMapper.selectListByProductId(productId);
193   -
194   - // 3. 生成 TDengine 的数据模型
195   - dbStructureDataService.createSuperTableDataModel(product, thingModelList);
196   - }
197   -
198   - @Override
199   - public List<IotProductThingModelDO> getProductThingModelListByProductKey(String productKey) {
200   - return productThingModelMapper.selectListByProductKey(productKey);
201   - }
202   -
203   - /**
204   - * 创建默认的事件和服务
205   - */
206   - public void createDefaultEventsAndServices(Long productId, String productKey) {
207   - // 1. 获取当前属性列表
208   - List<IotProductThingModelDO> propertyList = productThingModelMapper
209   - .selectListByProductIdAndType(productId, IotProductThingModelTypeEnum.PROPERTY.getType());
210   -
211   - // 2. 生成新的事件和服务列表
212   - List<IotProductThingModelDO> newThingModelList = new ArrayList<>();
213   - // 生成属性上报事件
214   - ThingModelEvent propertyPostEvent = generatePropertyPostEvent(propertyList);
215   - if (propertyPostEvent != null) {
216   - IotProductThingModelDO eventThingModel = buildEventThingModelDO(productId, productKey, propertyPostEvent);
217   - newThingModelList.add(eventThingModel);
218   - }
219   - // 生成属性设置服务
220   - ThingModelService propertySetService = generatePropertySetService(propertyList);
221   - if (propertySetService != null) {
222   - IotProductThingModelDO setServiceThingModel = buildServiceThingModelDO(productId, productKey, propertySetService);
223   - newThingModelList.add(setServiceThingModel);
224   - }
225   - // 生成属性获取服务
226   - ThingModelService propertyGetService = generatePropertyGetService(propertyList);
227   - if (propertyGetService != null) {
228   - IotProductThingModelDO getServiceThingModel = buildServiceThingModelDO(productId, productKey, propertyGetService);
229   - newThingModelList.add(getServiceThingModel);
230   - }
231   -
232   - // 3. 获取数据库中的默认的旧事件和服务列表
233   - List<IotProductThingModelDO> oldThingModelList = productThingModelMapper.selectListByProductIdAndIdentifiersAndTypes(
234   - productId,
235   - Arrays.asList("post", "set", "get"),
236   - Arrays.asList(IotProductThingModelTypeEnum.EVENT.getType(), IotProductThingModelTypeEnum.SERVICE.getType())
237   - );
238   -
239   - // 3.1 使用 diffList 方法比较新旧列表
240   - List<List<IotProductThingModelDO>> diffResult = diffList(oldThingModelList, newThingModelList,
241   - // 继续使用 identifier 和 type 进行比较:这样可以准确地匹配对应的功能对象。
242   - (oldFunc, newFunc) -> Objects.equals(oldFunc.getIdentifier(), newFunc.getIdentifier())
243   - && Objects.equals(oldFunc.getType(), newFunc.getType()));
244   - List<IotProductThingModelDO> createList = diffResult.get(0); // 需要新增的
245   - List<IotProductThingModelDO> updateList = diffResult.get(1); // 需要更新的
246   - List<IotProductThingModelDO> deleteList = diffResult.get(2); // 需要删除的
247   -
248   - // 3.2 批量执行数据库操作
249   - // 新增数据库中的新事件和服务列表
250   - if (CollUtil.isNotEmpty(createList)) {
251   - productThingModelMapper.insertBatch(createList);
252   - }
253   - // 更新数据库中的事件和服务列表
254   - if (CollUtil.isNotEmpty(updateList)) {
255   - // 首先,为每个需要更新的对象设置其对应的 ID
256   - updateList.forEach(updateFunc -> {
257   - IotProductThingModelDO oldFunc = findThingModelByIdentifierAndType(
258   - oldThingModelList, updateFunc.getIdentifier(), updateFunc.getType());
259   - if (oldFunc != null) {
260   - updateFunc.setId(oldFunc.getId());
261   - }
262   - });
263   - // 过滤掉没有设置 ID 的对象
264   - List<IotProductThingModelDO> validUpdateList = updateList.stream()
265   - .filter(func -> func.getId() != null)
266   - .collect(Collectors.toList());
267   - // 执行批量更新
268   - if (CollUtil.isNotEmpty(validUpdateList)) {
269   - productThingModelMapper.updateBatch(validUpdateList);
270   - }
271   - }
272   -
273   - // 删除数据库中的旧事件和服务列表
274   - if (CollUtil.isNotEmpty(deleteList)) {
275   - Set<Long> idsToDelete = CollectionUtils.convertSet(deleteList, IotProductThingModelDO::getId);
276   - productThingModelMapper.deleteByIds(idsToDelete);
277   - }
278   - }
279   -
280   - /**
281   - * 根据标识符和类型查找功能对象
282   - */
283   - private IotProductThingModelDO findThingModelByIdentifierAndType(List<IotProductThingModelDO> thingModelList,
284   - String identifier, Integer type) {
285   - return CollUtil.findOne(thingModelList, func ->
286   - Objects.equals(func.getIdentifier(), identifier) && Objects.equals(func.getType(), type));
287   - }
288   -
289   - /**
290   - * 构建事件功能对象
291   - */
292   - private IotProductThingModelDO buildEventThingModelDO(Long productId, String productKey, ThingModelEvent event) {
293   - return new IotProductThingModelDO()
294   - .setProductId(productId)
295   - .setProductKey(productKey)
296   - .setIdentifier(event.getIdentifier())
297   - .setName(event.getName())
298   - .setDescription(event.getDescription())
299   - .setType(IotProductThingModelTypeEnum.EVENT.getType())
300   - .setEvent(event);
301   - }
302   -
303   - /**
304   - * 构建服务功能对象
305   - */
306   - private IotProductThingModelDO buildServiceThingModelDO(Long productId, String productKey, ThingModelService service) {
307   - return new IotProductThingModelDO()
308   - .setProductId(productId)
309   - .setProductKey(productKey)
310   - .setIdentifier(service.getIdentifier())
311   - .setName(service.getName())
312   - .setDescription(service.getDescription())
313   - .setType(IotProductThingModelTypeEnum.SERVICE.getType())
314   - .setService(service);
315   - }
316   -
317   - /**
318   - * 生成属性上报事件
319   - */
320   - private ThingModelEvent generatePropertyPostEvent(List<IotProductThingModelDO> propertyList) {
321   - if (CollUtil.isEmpty(propertyList)) {
322   - return null;
323   - }
324   -
325   - ThingModelEvent event = new ThingModelEvent()
326   - .setIdentifier("post")
327   - .setName("属性上报")
328   - .setType("info")
329   - .setDescription("属性上报事件")
330   - .setMethod("thing.event.property.post");
331   -
332   - // 将属性列表转换为事件的输出参数
333   - List<ThingModelArgument> outputData = new ArrayList<>();
334   - for (IotProductThingModelDO thingModel : propertyList) {
335   - ThingModelArgument arg = new ThingModelArgument()
336   - .setIdentifier(thingModel.getIdentifier())
337   - .setName(thingModel.getName())
338   - .setProperty(thingModel.getProperty())
339   - .setDescription(thingModel.getDescription())
340   - .setDirection("output"); // 设置为输出参数
341   - outputData.add(arg);
342   - }
343   - event.setOutputData(outputData);
344   - return event;
345   - }
346   -
347   - /**
348   - * 生成属性设置服务
349   - */
350   - private ThingModelService generatePropertySetService(List<IotProductThingModelDO> propertyList) {
351   - if (propertyList == null || propertyList.isEmpty()) {
352   - return null;
353   - }
354   -
355   - List<ThingModelArgument> inputData = new ArrayList<>();
356   - for (IotProductThingModelDO thingModel : propertyList) {
357   - ThingModelProperty property = thingModel.getProperty();
358   - if (IotProductThingModelAccessModeEnum.READ_WRITE.getMode().equals(property.getAccessMode())) {
359   - ThingModelArgument arg = new ThingModelArgument()
360   - .setIdentifier(property.getIdentifier())
361   - .setName(property.getName())
362   - .setProperty(property)
363   - .setDescription(property.getDescription())
364   - .setDirection("input"); // 设置为输入参数
365   - inputData.add(arg);
366   - }
367   - }
368   - if (inputData.isEmpty()) {
369   - // 如果没有可写属性,不生成属性设置服务
370   - return null;
371   - }
372   -
373   - // 属性设置服务一般不需要输出参数
374   - return new ThingModelService()
375   - .setIdentifier("set")
376   - .setName("属性设置")
377   - .setCallType("async")
378   - .setDescription("属性设置服务")
379   - .setMethod("thing.service.property.set")
380   - .setInputData(inputData)
381   - // 属性设置服务一般不需要输出参数
382   - .setOutputData(new ArrayList<>());
383   - }
384   -
385   - /**
386   - * 生成属性获取服务
387   - */
388   - private ThingModelService generatePropertyGetService(List<IotProductThingModelDO> propertyList) {
389   - if (propertyList == null || propertyList.isEmpty()) {
390   - return null;
391   - }
392   -
393   - List<ThingModelArgument> outputData = new ArrayList<>();
394   - for (IotProductThingModelDO thingModelDO : propertyList) {
395   - ThingModelProperty property = thingModelDO.getProperty();
396   - if (ObjectUtils.equalsAny(property.getAccessMode(),
397   - IotProductThingModelAccessModeEnum.READ_ONLY.getMode(), IotProductThingModelAccessModeEnum.READ_WRITE.getMode())) {
398   - ThingModelArgument arg = new ThingModelArgument()
399   - .setIdentifier(property.getIdentifier())
400   - .setName(property.getName())
401   - .setProperty(property)
402   - .setDescription(property.getDescription())
403   - .setDirection("output"); // 设置为输出参数
404   - outputData.add(arg);
405   - }
406   - }
407   - if (outputData.isEmpty()) {
408   - // 如果没有可读属性,不生成属性获取服务
409   - return null;
410   - }
411   -
412   - ThingModelService service = new ThingModelService()
413   - .setIdentifier("get")
414   - .setName("属性获取")
415   - .setCallType("async")
416   - .setDescription("属性获取服务")
417   - .setMethod("thing.service.property.get");
418   -
419   - // 定义输入参数:属性标识符列表
420   - ThingModelArgument inputArg = new ThingModelArgument()
421   - .setIdentifier("properties")
422   - .setName("属性标识符列表")
423   - .setDescription("需要获取的属性标识符列表")
424   - .setDirection("input"); // 设置为输入参数
425   -
426   - // 创建数组类型,元素类型为文本类型(字符串)TODO @puhui999: 还得研究研究
427   - ThingModelArrayDataSpecs arrayType = new ThingModelArrayDataSpecs();
428   - arrayType.setDataType("array");
429   - inputArg.setProperty(new ThingModelProperty().setIdentifier(inputArg.getIdentifier()).setName(inputArg.getName())
430   - .setDescription(inputArg.getDescription()).setDataSpecs(arrayType));
431   -
432   - ThingModelDateOrTextDataSpecs textType = new ThingModelDateOrTextDataSpecs();
433   - textType.setDataType("text");
434   - inputArg.setProperty(new ThingModelProperty().setIdentifier(inputArg.getIdentifier()).setName(inputArg.getName())
435   - .setDescription(inputArg.getDescription()).setDataSpecs(textType));
436   -
437   - service.setInputData(Collections.singletonList(inputArg));
438   - service.setOutputData(outputData);
439   - return service;
440   - }
441   -
442   -}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotProductThingModelService.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelService.java
1 1 package cn.iocoder.yudao.module.iot.service.thingmodel;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelPageReqVO;
5   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotProductThingModelSaveReqVO;
6   -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
  4 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelPageReqVO;
  5 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelSaveReqVO;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
7 7 import jakarta.validation.Valid;
8 8  
9 9 import java.util.List;
... ... @@ -13,7 +13,7 @@ import java.util.List;
13 13 *
14 14 * @author 芋道源码
15 15 */
16   -public interface IotProductThingModelService {
  16 +public interface IotThingModelService {
17 17  
18 18 /**
19 19 * 创建产品物模型
... ... @@ -21,21 +21,21 @@ public interface IotProductThingModelService {
21 21 * @param createReqVO 创建信息
22 22 * @return 编号
23 23 */
24   - Long createProductThingModel(@Valid IotProductThingModelSaveReqVO createReqVO);
  24 + Long createThingModel(@Valid IotThingModelSaveReqVO createReqVO);
25 25  
26 26 /**
27 27 * 更新产品物模型
28 28 *
29 29 * @param updateReqVO 更新信息
30 30 */
31   - void updateProductThingModel(@Valid IotProductThingModelSaveReqVO updateReqVO);
  31 + void updateThingModel(@Valid IotThingModelSaveReqVO updateReqVO);
32 32  
33 33 /**
34 34 * 删除产品物模型
35 35 *
36 36 * @param id 编号
37 37 */
38   - void deleteProductThingModel(Long id);
  38 + void deleteThingModel(Long id);
39 39  
40 40 /**
41 41 * 获得产品物模型
... ... @@ -43,7 +43,7 @@ public interface IotProductThingModelService {
43 43 * @param id 编号
44 44 * @return 产品物模型
45 45 */
46   - IotProductThingModelDO getProductThingModel(Long id);
  46 + IotThingModelDO getThingModel(Long id);
47 47  
48 48 /**
49 49 * 获得产品物模型列表
... ... @@ -51,7 +51,7 @@ public interface IotProductThingModelService {
51 51 * @param productId 产品编号
52 52 * @return 产品物模型列表
53 53 */
54   - List<IotProductThingModelDO> getProductThingModelListByProductId(Long productId);
  54 + List<IotThingModelDO> getThingModelListByProductId(Long productId);
55 55  
56 56 /**
57 57 * 获得产品物模型分页
... ... @@ -59,14 +59,7 @@ public interface IotProductThingModelService {
59 59 * @param pageReqVO 分页查询
60 60 * @return 产品物模型分页
61 61 */
62   - PageResult<IotProductThingModelDO> getProductThingModelPage(IotProductThingModelPageReqVO pageReqVO);
63   -
64   - /**
65   - * 创建超级表数据模型
66   - *
67   - * @param productId 产品编号
68   - */
69   - void createSuperTableDataModel(Long productId);
  62 + PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO);
70 63  
71 64 /**
72 65 * 获得产品物模型列表
... ... @@ -74,6 +67,6 @@ public interface IotProductThingModelService {
74 67 * @param productKey 产品 Key
75 68 * @return 产品物模型列表
76 69 */
77   - List<IotProductThingModelDO> getProductThingModelListByProductKey(String productKey);
  70 + List<IotThingModelDO> getProductThingModelListByProductKey(String productKey);
78 71  
79 72 }
80 73 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.thingmodel;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent;
  8 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelParam;
  9 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
  10 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelPageReqVO;
  11 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo.IotThingModelSaveReqVO;
  12 +import cn.iocoder.yudao.module.iot.convert.thingmodel.IotThingModelConvert;
  13 +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
  14 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
  15 +import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper;
  16 +import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
  17 +import cn.iocoder.yudao.module.iot.enums.thingmodel.*;
  18 +import cn.iocoder.yudao.module.iot.service.product.IotProductService;
  19 +import jakarta.annotation.Resource;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.springframework.stereotype.Service;
  22 +import org.springframework.transaction.annotation.Transactional;
  23 +import org.springframework.validation.annotation.Validated;
  24 +
  25 +import java.util.*;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  29 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
  30 +
  31 +/**
  32 + * IoT 产品物模型 Service 实现类
  33 + *
  34 + * @author 芋道源码
  35 + */
  36 +@Service
  37 +@Validated
  38 +@Slf4j
  39 +public class IotThingModelServiceImpl implements IotThingModelService {
  40 +
  41 + @Resource
  42 + private IotThingModelMapper thingModelMapper;
  43 +
  44 + @Resource
  45 + private IotProductService productService;
  46 +
  47 + @Override
  48 + @Transactional(rollbackFor = Exception.class)
  49 + public Long createThingModel(IotThingModelSaveReqVO createReqVO) {
  50 + // 1. 校验功能标识符在同一产品下是否唯一
  51 + validateIdentifierUnique(createReqVO.getProductId(), createReqVO.getIdentifier());
  52 +
  53 + // 2. 功能名称在同一产品下是否唯一
  54 + validateNameUnique(createReqVO.getProductId(), createReqVO.getName());
  55 +
  56 + // 3. 系统保留字段,不能用于标识符定义
  57 + validateNotDefaultEventAndService(createReqVO.getIdentifier());
  58 +
  59 + // 4. 校验产品状态,发布状态下,不允许新增功能
  60 + validateProductStatus(createReqVO.getProductId());
  61 +
  62 + // 5. 插入数据库
  63 + IotThingModelDO thingModel = IotThingModelConvert.INSTANCE.convert(createReqVO);
  64 + thingModelMapper.insert(thingModel);
  65 +
  66 + // 6. 如果创建的是属性,需要更新默认的事件和服务
  67 + if (Objects.equals(createReqVO.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
  68 + createDefaultEventsAndServices(createReqVO.getProductId(), createReqVO.getProductKey());
  69 + }
  70 + // TODO @puhui999: 服务和事件的情况 method 怎么设置?在前端设置还是后端设置?
  71 + return thingModel.getId();
  72 + }
  73 +
  74 + @Override
  75 + @Transactional(rollbackFor = Exception.class)
  76 + public void updateThingModel(IotThingModelSaveReqVO updateReqVO) {
  77 + // 1. 校验功能是否存在
  78 + validateProductThingModelMapperExists(updateReqVO.getId());
  79 +
  80 + // 2. 校验功能标识符是否唯一
  81 + validateIdentifierUniqueForUpdate(updateReqVO.getId(), updateReqVO.getProductId(), updateReqVO.getIdentifier());
  82 +
  83 + // 3. 校验产品状态,发布状态下,不允许操作功能
  84 + validateProductStatus(updateReqVO.getProductId());
  85 +
  86 + // 4. 更新数据库
  87 + IotThingModelDO thingModel = IotThingModelConvert.INSTANCE.convert(updateReqVO);
  88 + thingModelMapper.updateById(thingModel);
  89 +
  90 + // 5. 如果更新的是属性,需要更新默认的事件和服务
  91 + if (Objects.equals(updateReqVO.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
  92 + createDefaultEventsAndServices(updateReqVO.getProductId(), updateReqVO.getProductKey());
  93 + }
  94 + }
  95 +
  96 + @Override
  97 + @Transactional(rollbackFor = Exception.class)
  98 + public void deleteThingModel(Long id) {
  99 + // 1. 校验功能是否存在
  100 + IotThingModelDO thingModel = thingModelMapper.selectById(id);
  101 + if (thingModel == null) {
  102 + throw exception(THING_MODEL_NOT_EXISTS);
  103 + }
  104 +
  105 + // 3. 校验产品状态,发布状态下,不允许操作功能
  106 + validateProductStatus(thingModel.getProductId());
  107 +
  108 + // 2. 删除功能
  109 + thingModelMapper.deleteById(id);
  110 +
  111 + // 3. 如果删除的是属性,需要更新默认的事件和服务
  112 + if (Objects.equals(thingModel.getType(), IotThingModelTypeEnum.PROPERTY.getType())) {
  113 + createDefaultEventsAndServices(thingModel.getProductId(), thingModel.getProductKey());
  114 + }
  115 + }
  116 +
  117 + @Override
  118 + public IotThingModelDO getThingModel(Long id) {
  119 + return thingModelMapper.selectById(id);
  120 + }
  121 +
  122 + @Override
  123 + public List<IotThingModelDO> getThingModelListByProductId(Long productId) {
  124 + return thingModelMapper.selectListByProductId(productId);
  125 + }
  126 +
  127 + @Override
  128 + public PageResult<IotThingModelDO> getProductThingModelPage(IotThingModelPageReqVO pageReqVO) {
  129 + return thingModelMapper.selectPage(pageReqVO);
  130 + }
  131 +
  132 + @Override
  133 + public List<IotThingModelDO> getProductThingModelListByProductKey(String productKey) {
  134 + return thingModelMapper.selectListByProductKey(productKey);
  135 + }
  136 +
  137 + /**
  138 + * 校验功能是否存在
  139 + *
  140 + * @param id 功能编号
  141 + */
  142 + private void validateProductThingModelMapperExists(Long id) {
  143 + if (thingModelMapper.selectById(id) == null) {
  144 + throw exception(THING_MODEL_NOT_EXISTS);
  145 + }
  146 + }
  147 +
  148 + private void validateIdentifierUniqueForUpdate(Long id, Long productId, String identifier) {
  149 + IotThingModelDO thingModel = thingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
  150 + if (thingModel != null && ObjectUtil.notEqual(thingModel.getId(), id)) {
  151 + throw exception(THING_MODEL_IDENTIFIER_EXISTS);
  152 + }
  153 + }
  154 +
  155 + private void validateProductStatus(Long createReqVO) {
  156 + IotProductDO product = productService.getProduct(createReqVO);
  157 + if (Objects.equals(product.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
  158 + throw exception(PRODUCT_STATUS_NOT_ALLOW_THING_MODEL);
  159 + }
  160 + }
  161 +
  162 + private void validateNotDefaultEventAndService(String identifier) {
  163 + // set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义
  164 + if (CollUtil.containsAny(Arrays.asList("set", "get", "post", "property", "event", "time", "value"), Collections.singletonList(identifier))) {
  165 + throw exception(THING_MODEL_IDENTIFIER_INVALID);
  166 + }
  167 + }
  168 +
  169 + private void validateNameUnique(Long productId, String name) {
  170 + IotThingModelDO thingModel = thingModelMapper.selectByProductIdAndName(productId, name);
  171 + if (thingModel != null) {
  172 + throw exception(THING_MODEL_NAME_EXISTS);
  173 + }
  174 + }
  175 +
  176 + private void validateIdentifierUnique(Long productId, String identifier) {
  177 + IotThingModelDO thingModel = thingModelMapper.selectByProductIdAndIdentifier(productId, identifier);
  178 + if (thingModel != null) {
  179 + throw exception(THING_MODEL_IDENTIFIER_EXISTS);
  180 + }
  181 + }
  182 +
  183 + /**
  184 + * 创建默认的事件和服务
  185 + */
  186 + public void createDefaultEventsAndServices(Long productId, String productKey) {
  187 + // 1. 获取当前属性列表
  188 + List<IotThingModelDO> properties = thingModelMapper
  189 + .selectListByProductIdAndType(productId, IotThingModelTypeEnum.PROPERTY.getType());
  190 +
  191 + // 2. 生成新的事件和服务列表
  192 + List<IotThingModelDO> newThingModels = new ArrayList<>();
  193 + // 2.1 生成属性上报事件
  194 + ThingModelEvent propertyPostEvent = generatePropertyPostEvent(properties);
  195 + if (propertyPostEvent != null) {
  196 + newThingModels.add(buildEventThingModelDO(productId, productKey, propertyPostEvent, "属性上报事件"));
  197 + }
  198 + // 2.2 生成属性设置服务
  199 + ThingModelService propertySetService = generatePropertySetService(properties);
  200 + if (propertySetService != null) {
  201 + newThingModels.add(buildServiceThingModelDO(productId, productKey, propertySetService, "属性设置服务"));
  202 + }
  203 + // 2.3 生成属性获取服务
  204 + ThingModelService propertyGetService = generatePropertyGetService(properties);
  205 + if (propertyGetService != null) {
  206 + newThingModels.add(buildServiceThingModelDO(productId, productKey, propertyGetService, "属性获取服务"));
  207 + }
  208 +
  209 + // 3.1 获取数据库中的默认的旧事件和服务列表
  210 + List<IotThingModelDO> oldThingModels = thingModelMapper.selectListByProductIdAndIdentifiersAndTypes(
  211 + productId,
  212 + Arrays.asList("post", "set", "get"),
  213 + Arrays.asList(IotThingModelTypeEnum.EVENT.getType(), IotThingModelTypeEnum.SERVICE.getType())
  214 + );
  215 + // 3.2 创建默认的事件和服务
  216 + createDefaultEventsAndServices(oldThingModels, newThingModels);
  217 + }
  218 +
  219 + /**
  220 + * 创建默认的事件和服务
  221 + */
  222 + private void createDefaultEventsAndServices(List<IotThingModelDO> oldThingModels,
  223 + List<IotThingModelDO> newThingModels) {
  224 + // 使用 diffList 方法比较新旧列表
  225 + List<List<IotThingModelDO>> diffResult = diffList(oldThingModels, newThingModels,
  226 + (oldVal, newVal) -> {
  227 + // 继续使用 identifier 和 type 进行比较:这样可以准确地匹配对应的功能对象。
  228 + boolean same = Objects.equals(oldVal.getIdentifier(), newVal.getIdentifier())
  229 + && Objects.equals(oldVal.getType(), newVal.getType());
  230 + if (same) {
  231 + newVal.setId(oldVal.getId()); // 设置编号
  232 + }
  233 + return same;
  234 + });
  235 + // 批量添加、修改、删除
  236 + if (CollUtil.isNotEmpty(diffResult.get(0))) {
  237 + thingModelMapper.insertBatch(diffResult.get(0));
  238 + }
  239 + if (CollUtil.isNotEmpty(diffResult.get(1))) {
  240 + thingModelMapper.updateBatch(diffResult.get(1));
  241 + }
  242 + if (CollUtil.isNotEmpty(diffResult.get(2))) {
  243 + thingModelMapper.deleteByIds(convertSet(diffResult.get(2), IotThingModelDO::getId));
  244 + }
  245 + }
  246 +
  247 + /**
  248 + * 构建事件功能对象
  249 + */
  250 + private IotThingModelDO buildEventThingModelDO(Long productId, String productKey,
  251 + ThingModelEvent event, String description) {
  252 + return new IotThingModelDO().setProductId(productId).setProductKey(productKey)
  253 + .setIdentifier(event.getIdentifier()).setName(event.getName()).setDescription(description)
  254 + .setType(IotThingModelTypeEnum.EVENT.getType()).setEvent(event);
  255 + }
  256 +
  257 + /**
  258 + * 构建服务功能对象
  259 + */
  260 + private IotThingModelDO buildServiceThingModelDO(Long productId, String productKey,
  261 + ThingModelService service, String description) {
  262 + return new IotThingModelDO().setProductId(productId).setProductKey(productKey)
  263 + .setIdentifier(service.getIdentifier()).setName(service.getName()).setDescription(description)
  264 + .setType(IotThingModelTypeEnum.SERVICE.getType()).setService(service);
  265 + }
  266 +
  267 + /**
  268 + * 生成属性上报事件
  269 + */
  270 + private ThingModelEvent generatePropertyPostEvent(List<IotThingModelDO> thingModels) {
  271 + // 没有属性则不生成
  272 + if (CollUtil.isEmpty(thingModels)) {
  273 + return null;
  274 + }
  275 +
  276 + // 生成属性上报事件
  277 + return new ThingModelEvent().setIdentifier("post").setName("属性上报").setMethod("thing.event.property.post")
  278 + .setType(IotThingModelServiceEventTypeEnum.INFO.getType())
  279 + .setOutputParams(buildInputOutputParam(thingModels, IotThingModelParamDirectionEnum.OUTPUT));
  280 + }
  281 +
  282 + /**
  283 + * 生成属性设置服务
  284 + */
  285 + private ThingModelService generatePropertySetService(List<IotThingModelDO> thingModels) {
  286 + // 1.1 过滤出所有可写属性
  287 + thingModels = filterList(thingModels, thingModel ->
  288 + IotThingModelAccessModeEnum.READ_WRITE.getMode().equals(thingModel.getProperty().getAccessMode()));
  289 + // 1.2 没有可写属性则不生成
  290 + if (CollUtil.isEmpty(thingModels)) {
  291 + return null;
  292 + }
  293 +
  294 + // 2. 生成属性设置服务
  295 + return new ThingModelService().setIdentifier("set").setName("属性设置").setMethod("thing.service.property.set")
  296 + .setCallType(IotThingModelServiceCallTypeEnum.ASYNC.getType())
  297 + .setInputParams(buildInputOutputParam(thingModels, IotThingModelParamDirectionEnum.INPUT))
  298 + .setOutputParams(Collections.emptyList()); // 属性设置服务一般不需要输出参数
  299 + }
  300 +
  301 + /**
  302 + * 生成属性获取服务
  303 + */
  304 + private ThingModelService generatePropertyGetService(List<IotThingModelDO> thingModels) {
  305 + // 1.1 没有属性则不生成
  306 + if (CollUtil.isEmpty(thingModels)) {
  307 + return null;
  308 + }
  309 +
  310 + // 1.2 生成属性获取服务
  311 + return new ThingModelService().setIdentifier("get").setName("属性获取").setMethod("thing.service.property.get")
  312 + .setCallType(IotThingModelServiceCallTypeEnum.ASYNC.getType())
  313 + .setInputParams(buildInputOutputParam(thingModels, IotThingModelParamDirectionEnum.INPUT))
  314 + .setOutputParams(buildInputOutputParam(thingModels, IotThingModelParamDirectionEnum.OUTPUT));
  315 + }
  316 +
  317 + /**
  318 + * 构建输入/输出参数列表
  319 + *
  320 + * @param thingModels 属性列表
  321 + * @return 输入/输出参数列表
  322 + */
  323 + private List<ThingModelParam> buildInputOutputParam(List<IotThingModelDO> thingModels,
  324 + IotThingModelParamDirectionEnum direction) {
  325 + return convertList(thingModels, thingModel ->
  326 + BeanUtils.toBean(thingModel.getProperty(), ThingModelParam.class).setParaOrder(0) // TODO @puhui999: 先搞个默认值看看怎么个事
  327 + .setDirection(direction.getDirection()));
  328 + }
  329 +
  330 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/IotTdDatabaseUtils.java
1 1 package cn.iocoder.yudao.module.iot.util;
2 2  
  3 +import cn.hutool.core.lang.Assert;
3 4 import cn.hutool.core.util.StrUtil;
4 5 import cn.iocoder.yudao.module.iot.enums.IotConstants;
5 6 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
6   -import org.springframework.beans.factory.annotation.Value;
7   -import org.springframework.stereotype.Component;
8   -
9   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
10   -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_DEVICE_NOT_EXISTS;
11 7  
12 8 // TODO @芋艿:可能要思索下,有没更好的处理方式
13 9 // TODO @芋艿:怎么改成无状态
... ... @@ -34,20 +30,17 @@ public class IotTdDatabaseUtils {
34 30 * @return 产品超级表表名
35 31 */
36 32 public static String getProductSuperTableName(Integer deviceType, String productKey) {
37   - // TODO @alwayssuper:枚举字段,不要 1、2、3;不符合预期,抛出异常
38   - if (deviceType == null) {
39   - throw exception(PRODUCT_DEVICE_NOT_EXISTS);
40   - }
  33 + Assert.notNull(deviceType, "deviceType 不能为空");
41 34 if (IotProductDeviceTypeEnum.GATEWAY_SUB.getType().equals(deviceType)) {
42 35 return String.format(IotConstants.GATEWAY_SUB_STABLE_NAME_FORMAT, productKey).toLowerCase();
43   - } else if (IotProductDeviceTypeEnum.GATEWAY.getType().equals(deviceType)) {
  36 + }
  37 + if (IotProductDeviceTypeEnum.GATEWAY.getType().equals(deviceType)) {
44 38 return String.format(IotConstants.GATEWAY_STABLE_NAME_FORMAT, productKey).toLowerCase();
45   - } else if (IotProductDeviceTypeEnum.DIRECT.getType().equals(deviceType)){
46   - return String.format(IotConstants.DEVICE_STABLE_NAME_FORMAT, productKey).toLowerCase();
47 39 }
48   - else{
49   - throw exception(PRODUCT_DEVICE_NOT_EXISTS);
  40 + if (IotProductDeviceTypeEnum.DIRECT.getType().equals(deviceType)){
  41 + return String.format(IotConstants.DEVICE_STABLE_NAME_FORMAT, productKey).toLowerCase();
50 42 }
  43 + throw new IllegalArgumentException("deviceType 不正确");
51 44 }
52 45  
53 46 /**
... ... @@ -58,7 +51,6 @@ public class IotTdDatabaseUtils {
58 51 *
59 52 */
60 53 public static String getThingModelMessageSuperTableName(String productKey) {
61   - // TODO @alwayssuper:是不是应该 + 拼接就好,不用 format
62 54 return "thing_model_message_" + productKey.toLowerCase();
63 55 }
64 56  
... ... @@ -70,7 +62,8 @@ public class IotTdDatabaseUtils {
70 62 * @return 物模型日志设备表名
71 63 */
72 64 public static String getThingModelMessageDeviceTableName(String productKey, String deviceName) {
73   - return String.format(IotConstants.THING_MODEL_MESSAGE_TABLE_NAME_FORMAT, productKey.toLowerCase(), deviceName.toLowerCase());
  65 + return String.format(IotConstants.THING_MODEL_MESSAGE_TABLE_NAME_FORMAT,
  66 + productKey.toLowerCase(), deviceName.toLowerCase());
74 67 }
75 68  
76 69 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDevicePropertyDataMapper.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE mapper
  3 + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4 + "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5 +<mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper">
  6 +
  7 + <select id="getProductPropertySTableFieldList" resultType="cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField">
  8 + DESCRIBE product_property_${productKey}
  9 + </select>
  10 +
  11 + <update id="createProductPropertySTable">
  12 + CREATE STABLE product_property_${productKey}
  13 + <foreach item="field" collection="fields" separator="," open="(" close=")">
  14 + ${field.field} ${field.type}
  15 + <if test="field.length != null and field.length > 0">
  16 + (${field.length})
  17 + </if>
  18 + </foreach>
  19 + TAGS (
  20 + device_key NCHAR(50)
  21 + )
  22 + </update>
  23 +
  24 + <update id="alterProductPropertySTableAddField">
  25 + ALTER STABLE product_property_${productKey}
  26 + ADD COLUMN ${field.field} ${field.type}
  27 + <if test="field.length != null and field.length > 0">
  28 + (${field.length})
  29 + </if>
  30 + </update>
  31 +
  32 + <update id="alterProductPropertySTableModifyField">
  33 + ALTER STABLE product_property_${productKey}
  34 + MODIFY COLUMN ${field.field} ${field.type}
  35 + <if test="field.length != null and field.length > 0">
  36 + (${field.length})
  37 + </if>
  38 + </update>
  39 +
  40 + <update id="alterProductPropertySTableDropField">
  41 + ALTER STABLE product_property_${productKey}
  42 + DROP COLUMN ${field.field}
  43 + </update>
  44 +
  45 +</mapper>
0 46 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/plugininstance/PluginInstanceMapper.xml
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3   -<mapper namespace="cn.iocoder.yudao.module.iot.dal.mysql.plugininstance.PluginInstanceMapper">
  3 +<mapper namespace="cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInstanceMapper">
4 4  
5 5 <!--
6 6 一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdThinkModelMessageMapper.xml
... ... @@ -5,7 +5,6 @@
5 5 <mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.TdThingModelMessageMapper">
6 6  
7 7 <!-- 创建物模型消息日志超级表 -->
8   - <!-- TODO @芋艿:捉摸下字段,特别是 sys、ts 这种缩写 -->
9 8 <update id="createSuperTable">
10 9 CREATE STABLE ${dataBaseName}.${superTableName}(
11 10 ts TIMESTAMP,
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml
... ... @@ -24,6 +24,7 @@
24 24 <plugin.class>cn.iocoder.yudao.module.iot.plugin.HttpPlugin</plugin.class>
25 25 <plugin.version>0.0.1</plugin.version>
26 26 <plugin.provider>ahh</plugin.provider>
  27 + <plugin.description>http-plugin-0.0.1</plugin.description>
27 28 <plugin.dependencies/>
28 29 </properties>
29 30  
... ... @@ -104,6 +105,7 @@
104 105 <Plugin-Class>${plugin.class}</Plugin-Class>
105 106 <Plugin-Version>${plugin.version}</Plugin-Version>
106 107 <Plugin-Provider>${plugin.provider}</Plugin-Provider>
  108 + <Plugin-Description>${plugin.description}</Plugin-Description>
107 109 <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
108 110 </manifestEntries>
109 111 </archive>
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java
... ... @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.plugin;
2 2  
3 3 import cn.hutool.json.JSONObject;
4 4 import cn.hutool.json.JSONUtil;
5   -import cn.iocoder.yudao.module.iot.api.DeviceDataApi;
  5 +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
6 6 import io.netty.buffer.Unpooled;
7 7 import io.netty.channel.ChannelFutureListener;
8 8 import io.netty.channel.ChannelHandlerContext;
... ... @@ -12,12 +12,9 @@ import io.netty.util.CharsetUtil;
12 12  
13 13 /**
14 14 * 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。
15   - * <p>
16   - * 请求格式:
17   - * POST /sys/{productKey}/{deviceName}/thing/event/property/post
18   - * 请求体为 JSON 格式数据。
19   - * <p>
20   - * 返回结果为 JSON 格式,包含统一的 code、data、id、message、method、version 字段。
  15 + *
  16 + * 1. 请求格式:JSON 格式,地址为 POST /sys/{productKey}/{deviceName}/thing/event/property/post
  17 + * 2. 返回结果:JSON 格式,包含统一的 code、data、id、message、method、version 字段
21 18 */
22 19 public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
23 20  
... ... @@ -29,10 +26,9 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
29 26  
30 27 @Override
31 28 protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
32   - String uri = request.uri();
33   -
34 29 // 期望的路径格式: /sys/{productKey}/{deviceName}/thing/event/property/post
35 30 // 使用 "/" 拆分路径
  31 + String uri = request.uri();
36 32 String[] parts = uri.split("/");
37 33  
38 34 /*
... ... @@ -52,25 +48,19 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
52 48 && "event".equals(parts[5])
53 49 && "property".equals(parts[6])
54 50 && "post".equals(parts[7]);
55   -
56 51 if (!isCorrectPath) {
57   - // 如果路径不匹配,返回 404 错误
58 52 writeResponse(ctx, HttpResponseStatus.NOT_FOUND, "Not Found");
59 53 return;
60 54 }
61   -
62 55 String productKey = parts[2];
63 56 String deviceName = parts[3];
64 57  
65   - // 从请求中获取原始数据
  58 + // 从请求中获取原始数据,尝试解析请求数据为 JSON 对象
66 59 String requestBody = request.content().toString(CharsetUtil.UTF_8);
67   -
68   - // 尝试解析请求数据为 JSON 对象
69 60 JSONObject jsonData;
70 61 try {
71 62 jsonData = JSONUtil.parseObj(requestBody);
72 63 } catch (Exception e) {
73   - // 数据不是合法的 JSON 格式,返回 400 错误
74 64 JSONObject res = createResponseJson(
75 65 400,
76 66 new JSONObject(),
... ... @@ -82,8 +72,6 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
82 72 writeResponse(ctx, HttpResponseStatus.BAD_REQUEST, res.toString());
83 73 return;
84 74 }
85   -
86   - // 获取请求中的 id 字段,若不存在则为 null
87 75 String id = jsonData.getStr("id", null);
88 76  
89 77 try {
... ... @@ -101,7 +89,6 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
101 89 );
102 90 writeResponse(ctx, HttpResponseStatus.OK, successRes.toString());
103 91 } catch (Exception e) {
104   - // 保存数据过程中出现异常,返回 500 错误
105 92 JSONObject errorRes = createResponseJson(
106 93 500,
107 94 new JSONObject(),
... ... @@ -128,7 +115,7 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
128 115 private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, String version) {
129 116 JSONObject res = new JSONObject();
130 117 res.set("code", code);
131   - res.set("data", data != null ? data : new JSONObject()); // 确保 data 不为 null
  118 + res.set("data", data != null ? data : new JSONObject());
132 119 res.set("id", id);
133 120 res.set("message", message);
134 121 res.set("method", method);
... ... @@ -137,24 +124,24 @@ public class HttpHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
137 124 }
138 125  
139 126 /**
140   - * 向客户端返回 HTTP 响应的辅助方法。
  127 + * 向客户端返回 HTTP 响应的辅助方法
141 128 *
142 129 * @param ctx 通道上下文
143 130 * @param status HTTP 响应状态码(网络层面的)
144 131 * @param content 响应内容(JSON 字符串或其他文本)
145 132 */
146 133 private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String content) {
  134 + // 设置响应头为 JSON 类型和正确的编码
147 135 FullHttpResponse response = new DefaultFullHttpResponse(
148 136 HttpVersion.HTTP_1_1,
149 137 status,
150 138 Unpooled.copiedBuffer(content, CharsetUtil.UTF_8)
151 139 );
152   -
153   - // 设置响应头为 JSON 类型和正确的编码
154 140 response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
155 141 response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
156 142  
157 143 // 发送响应并在发送完成后关闭连接
158 144 ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
159 145 }
  146 +
160 147 }
161 148 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java
1 1 package cn.iocoder.yudao.module.iot.plugin;
2 2  
3   -import cn.iocoder.yudao.module.iot.api.DeviceDataApi;
  3 +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
4 4 import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
5 5 import io.netty.bootstrap.ServerBootstrap;
6 6 import io.netty.channel.*;
... ... @@ -18,18 +18,25 @@ import java.util.concurrent.Executors;
18 18 public class HttpPlugin extends Plugin {
19 19  
20 20 private static final int PORT = 8092;
21   - private final ExecutorService executorService;
22   - private DeviceDataApi deviceDataApi; // 用于保存设备数据的 API
  21 +
  22 + private ExecutorService executorService;
  23 + private DeviceDataApi deviceDataApi;
23 24  
24 25 public HttpPlugin(PluginWrapper wrapper) {
25 26 super(wrapper);
26   - this.executorService = Executors.newSingleThreadExecutor(); // 创建单线程池
  27 + // 初始化线程池
  28 + this.executorService = Executors.newSingleThreadExecutor();
27 29 }
28 30  
29 31 @Override
30 32 public void start() {
31 33 log.info("HttpPlugin.start()");
32 34  
  35 + // 重新初始化线程池,确保它是活跃的
  36 + if (executorService.isShutdown() || executorService.isTerminated()) {
  37 + executorService = Executors.newSingleThreadExecutor();
  38 + }
  39 +
33 40 // 从 ServiceRegistry 中获取主程序暴露的 DeviceDataApi 接口实例
34 41 deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class);
35 42 if (deviceDataApi == null) {
... ... @@ -44,10 +51,13 @@ public class HttpPlugin extends Plugin {
44 51 @Override
45 52 public void stop() {
46 53 log.info("HttpPlugin.stop()");
47   - executorService.shutdownNow(); // 停止线程池
  54 + // 停止线程池
  55 + executorService.shutdownNow();
48 56 }
49 57  
50   - // 启动 HTTP 服务
  58 + /**
  59 + * 启动 HTTP 服务
  60 + */
51 61 private void startHttpServer() {
52 62 EventLoopGroup bossGroup = new NioEventLoopGroup(1);
53 63 EventLoopGroup workerGroup = new NioEventLoopGroup();
... ... @@ -56,7 +66,8 @@ public class HttpPlugin extends Plugin {
56 66 ServerBootstrap bootstrap = new ServerBootstrap();
57 67 bootstrap.group(bossGroup, workerGroup)
58 68 .channel(NioServerSocketChannel.class)
59   - .childHandler(new ChannelInitializer<Channel>() {
  69 + .childHandler(new ChannelInitializer<>() {
  70 +
60 71 @Override
61 72 protected void initChannel(Channel channel) {
62 73 channel.pipeline().addLast(new HttpServerCodec());
... ... @@ -64,6 +75,7 @@ public class HttpPlugin extends Plugin {
64 75 // 将从 ServiceRegistry 获取的 deviceDataApi 传入处理器
65 76 channel.pipeline().addLast(new HttpHandler(deviceDataApi));
66 77 }
  78 +
67 79 });
68 80  
69 81 // 绑定端口并启动服务器
... ... @@ -72,10 +84,11 @@ public class HttpPlugin extends Plugin {
72 84 future.channel().closeFuture().sync();
73 85 } catch (InterruptedException e) {
74 86 Thread.currentThread().interrupt();
75   - log.error("HTTP 服务启动中断", e);
  87 + log.warn("HTTP 服务启动被中断", e);
76 88 } finally {
77 89 bossGroup.shutdownGracefully();
78 90 workerGroup.shutdownGracefully();
79 91 }
80 92 }
81   -}
  93 +
  94 +}
82 95 \ No newline at end of file
... ...