diff --git a/pom.xml b/pom.xml index d157279eb..908c3808f 100644 --- a/pom.xml +++ b/pom.xml @@ -15,15 +15,16 @@ yudao-module-system yudao-module-infra - yudao-module-member + - yudao-module-pay - yudao-module-mall + + + ${project.artifactId} diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 291418177..1cc3111cd 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -64,6 +64,7 @@ 2.7.0 3.0.6 4.1.113.Final + 1.2.5 3.5.0 4.11.0 @@ -596,6 +597,13 @@ + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + ${mqtt.version} + diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/db/TenantDatabaseInterceptor.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/db/TenantDatabaseInterceptor.java index 8ea1a96b8..e220f8bcf 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/db/TenantDatabaseInterceptor.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/db/TenantDatabaseInterceptor.java @@ -13,7 +13,7 @@ import java.util.Set; /** * 基于 MyBatis Plus 多租户的功能,实现 DB 层面的多租户的功能 * - * @author 芋道源码 + * @author */ public class TenantDatabaseInterceptor implements TenantLineHandler { diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java index d60473a48..c8b0dbd66 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java @@ -62,6 +62,10 @@ public class BannerApplicationRunner implements ApplicationRunner { if (isNotPresent("cn.iocoder.yudao.module.ai.framework.web.config.AiWebConfiguration")) { System.out.println("[AI 大模型 yudao-module-ai - 已禁用][参考 https://doc.iocoder.cn/ai/build/ 开启]"); } + // IOT 物联网 + if (isNotPresent("cn.iocoder.yudao.module.iot.framework.web.config.IotWebConfiguration")) { + System.out.println("[IOT 物联网 yudao-module-iot - 已禁用][参考 https://doc.iocoder.cn/iot/build/ 开启]"); + } }); } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java index 6628f116c..41c5cead6 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java @@ -378,6 +378,12 @@ public class GlobalExceptionHandler { return CommonResult.error(NOT_IMPLEMENTED.getCode(), "[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]"); } + // 9. IOT 物联网 + if (message.contains("iot_")) { + log.error("[IOT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]"); + return CommonResult.error(NOT_IMPLEMENTED.getCode(), + "[IOT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]"); + } return null; } diff --git a/yudao-module-iot/pom.xml b/yudao-module-iot/pom.xml new file mode 100644 index 000000000..069af1699 --- /dev/null +++ b/yudao-module-iot/pom.xml @@ -0,0 +1,25 @@ + + + + yudao + cn.iocoder.boot + ${revision} + + + yudao-module-iot-api + yudao-module-iot-biz + + 4.0.0 + + yudao-module-iot + pom + + ${project.artifactId} + + 物联网模块 + + + + \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-api/pom.xml b/yudao-module-iot/yudao-module-iot-api/pom.xml new file mode 100644 index 000000000..8a75b09bf --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/pom.xml @@ -0,0 +1,26 @@ + + + + yudao-module-iot + cn.iocoder.boot + ${revision} + + 4.0.0 + yudao-module-iot-api + jar + + ${project.artifactId} + + 物联网 模块 API,暴露给其它模块调用 + + + + + cn.iocoder.boot + yudao-common + + + + diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java new file mode 100644 index 000000000..7da0c665b --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java @@ -0,0 +1,6 @@ +/** + * 占位 + * + * TODO 芋艿:后续删除 + */ +package cn.iocoder.yudao.module.iot.api; diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java new file mode 100644 index 000000000..31702e1e6 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.iot.enums; + +import cn.iocoder.yudao.framework.common.exception.ErrorCode; + +/** + * iot 错误码枚举类 + *

+ * iot 系统,使用 1-050-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== IoT 产品相关 1-050-001-000 ============ + ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_050_001_000, "产品不存在"); + ErrorCode PRODUCT_IDENTIFICATION_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在"); + ErrorCode PRODUCT_STATUS_NOT_DELETE = new ErrorCode(1_050_001_002, "产品状是发布状态,不允许删除"); + + // ========== IoT 产品物模型 1-050-002-000 ============ + ErrorCode THINK_MODEL_FUNCTION_NOT_EXISTS = new ErrorCode(1_050_002_000, "产品物模型不存在"); + ErrorCode THINK_MODEL_FUNCTION_EXISTS_BY_PRODUCT_KEY = new ErrorCode(1_050_002_001, "ProductKey 对应的产品物模型已存在"); + ErrorCode THINK_MODEL_FUNCTION_IDENTIFIER_EXISTS = new ErrorCode(1_050_002_002, "存在重复的功能标识符。"); + ErrorCode THINK_MODEL_FUNCTION_NAME_EXISTS = new ErrorCode(1_050_002_003, "存在重复的功能名称。"); + ErrorCode THINK_MODEL_FUNCTION_IDENTIFIER_INVALID = new ErrorCode(1_050_002_003, "产品物模型标识无效"); + + // ========== IoT 设备 1-050-003-000 ============ + ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在"); + ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一"); + ErrorCode DEVICE_HAS_CHILDREN = new ErrorCode(1_050_003_002, "有子设备,不允许删除"); + ErrorCode DEVICE_NAME_CANNOT_BE_MODIFIED = new ErrorCode(1_050_003_003, "设备名称不能修改"); + ErrorCode DEVICE_PRODUCT_CANNOT_BE_MODIFIED = new ErrorCode(1_050_003_004, "产品不能修改"); + ErrorCode DEVICE_INVALID_DEVICE_STATUS = new ErrorCode(1_050_003_005, "无效的设备状态"); + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceStatusEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceStatusEnum.java new file mode 100644 index 000000000..5fd983dc0 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceStatusEnum.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.iot.enums.device; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; + +import java.util.Arrays; + +/** + * IoT 设备状态枚举 + * + * @author haohao + */ +@Getter +public enum IotDeviceStatusEnum implements IntArrayValuable { + + INACTIVE(0, "未激活"), + ONLINE(1, "在线"), + OFFLINE(2, "离线"), + DISABLED(3, "已禁用"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotDeviceStatusEnum::getStatus).toArray(); + + /** + * 状态 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + IotDeviceStatusEnum(Integer status, String name) { + this.status = status; + this.name = name; + } + + public static IotDeviceStatusEnum fromStatus(Integer status) { + for (IotDeviceStatusEnum value : values()) { + if (value.getStatus().equals(status)) { + return value; + } + } + return null; + } + + public static boolean isValidStatus(Integer status) { + return fromStatus(status) != null; + } + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotAccessModeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotAccessModeEnum.java new file mode 100644 index 000000000..64ece99ca --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotAccessModeEnum.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.iot.enums.product; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * IOT 访问方式枚举类 + * + * @author ahh + */ +@AllArgsConstructor +@Getter +public enum IotAccessModeEnum { + + READ("r"), + WRITE("w"), + READ_WRITE("rw"); + + private final String mode; + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotDataFormatEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotDataFormatEnum.java new file mode 100644 index 000000000..8a1afa070 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotDataFormatEnum.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.iot.enums.product; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 产品数据格式枚举类 + * + * @author ahh + * @see 阿里云 - 什么是消息解析 + */ +@AllArgsConstructor +@Getter +public enum IotDataFormatEnum implements IntArrayValuable { + + JSON(0, "标准数据格式(JSON)"), + CUSTOMIZE(1, "透传/自定义"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotDataFormatEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + /** + * 描述 + */ + private final String description; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotNetTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotNetTypeEnum.java new file mode 100644 index 000000000..718e86d13 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotNetTypeEnum.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.iot.enums.product; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * IOT 联网方式枚举类 + * + * @author ahh + */ +@AllArgsConstructor +@Getter +public enum IotNetTypeEnum implements IntArrayValuable { + + WIFI(0, "Wi-Fi"), + CELLULAR(1, "Cellular"), + ETHERNET(2, "Ethernet"), + OTHER(3, "其他"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotNetTypeEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + /** + * 描述 + */ + private final String description; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java new file mode 100644 index 000000000..99b75f3fb --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.iot.enums.product; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * IOT 产品的设备类型 + * + * @author ahh + */ +@AllArgsConstructor +@Getter +public enum IotProductDeviceTypeEnum implements IntArrayValuable { + + DIRECT(0, "直连设备"), + GATEWAY_CHILD(1, "网关子设备"), + GATEWAY(2, "网关设备"); + + /** + * 类型 + */ + private final Integer type; + + /** + * 描述 + */ + private final String description; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotProductDeviceTypeEnum::getType).toArray(); + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductFunctionTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductFunctionTypeEnum.java new file mode 100644 index 000000000..7a924997a --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductFunctionTypeEnum.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.iot.enums.product; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * IOT 产品功能(物模型)类型枚举类 + * + * @author ahh + */ +@AllArgsConstructor +@Getter +public enum IotProductFunctionTypeEnum implements IntArrayValuable { + + PROPERTY(1, "属性"), + SERVICE(2, "服务"), + EVENT(3, "事件"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotProductFunctionTypeEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + /** + * 描述 + */ + private final String description; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductStatusEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductStatusEnum.java new file mode 100644 index 000000000..e64a3d678 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductStatusEnum.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.iot.enums.product; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * IOT 产品的状态枚举类 + * + * @author ahh + */ +@AllArgsConstructor +@Getter +public enum IotProductStatusEnum implements IntArrayValuable { + + UNPUBLISHED(0, "开发中"), + PUBLISHED(1, "已发布"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotProductStatusEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + /** + * 描述 + */ + private final String description; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProtocolTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProtocolTypeEnum.java new file mode 100644 index 000000000..c36a37723 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProtocolTypeEnum.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.iot.enums.product; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * IOT 接入网关协议枚举类 + * + * @author ahh + */ +@AllArgsConstructor +@Getter +public enum IotProtocolTypeEnum implements IntArrayValuable { + + CUSTOM(0, "自定义"), + MODBUS(1, "Modbus"), + OPC_UA(2, "OPC UA"), + ZIGBEE(3, "ZigBee"), + BLE(4, "BLE"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotProtocolTypeEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + /** + * 描述 + */ + private final String description; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotValidateTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotValidateTypeEnum.java new file mode 100644 index 000000000..9a8092b7b --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotValidateTypeEnum.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.iot.enums.product; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * IOT 数据校验级别枚举类 + * + * @author ahh + */ +@AllArgsConstructor +@Getter +public enum IotValidateTypeEnum implements IntArrayValuable { + + WEAK(0, "弱校验"), + NONE(1, "免校验"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotValidateTypeEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + /** + * 描述 + */ + private final String description; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml new file mode 100644 index 000000000..e3f93086a --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml @@ -0,0 +1,64 @@ + + + + yudao-module-iot + cn.iocoder.boot + ${revision} + + 4.0.0 + jar + + yudao-module-iot-biz + + ${project.artifactId} + + 物联网 模块,主要实现 产品管理、设备管理、协议管理等功能。 + + + + + + cn.iocoder.boot + yudao-module-iot-api + ${revision} + + + + + cn.iocoder.boot + yudao-spring-boot-starter-web + + + + cn.iocoder.boot + yudao-spring-boot-starter-security + + + + + cn.iocoder.boot + yudao-spring-boot-starter-mybatis + + + + + cn.iocoder.boot + yudao-spring-boot-starter-test + + + + + cn.iocoder.boot + yudao-spring-boot-starter-excel + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + + + + diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java new file mode 100644 index 000000000..6d75f1cdd --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java @@ -0,0 +1,89 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDevicePageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceRespVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceSaveReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceStatusUpdateReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - IoT 设备") +@RestController +@RequestMapping("/iot/device") +@Validated +public class IotDeviceController { + + @Resource + private IotDeviceService deviceService; + + @PostMapping("/create") + @Operation(summary = "创建设备") + @PreAuthorize("@ss.hasPermission('iot:device:create')") + public CommonResult createDevice(@Valid @RequestBody IotDeviceSaveReqVO createReqVO) { + return success(deviceService.createDevice(createReqVO)); + } + + @PutMapping("/update-status") + @Operation(summary = "更新设备状态") + @PreAuthorize("@ss.hasPermission('iot:device:update')") + public CommonResult updateDeviceStatus(@Valid @RequestBody IotDeviceStatusUpdateReqVO updateReqVO) { + deviceService.updateDeviceStatus(updateReqVO); + return success(true); + } + + @PutMapping("/update") + @Operation(summary = "更新设备") + @PreAuthorize("@ss.hasPermission('iot:device:update')") + public CommonResult updateDevice(@Valid @RequestBody IotDeviceSaveReqVO updateReqVO) { + deviceService.updateDevice(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除设备") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('iot:device:delete')") + public CommonResult deleteDevice(@RequestParam("id") Long id) { + deviceService.deleteDevice(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得设备") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('iot:device:query')") + public CommonResult getDevice(@RequestParam("id") Long id) { + IotDeviceDO device = deviceService.getDevice(id); + return success(BeanUtils.toBean(device, IotDeviceRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得设备分页") + @PreAuthorize("@ss.hasPermission('iot:device:query')") + public CommonResult> getDevicePage(@Valid IotDevicePageReqVO pageReqVO) { + PageResult pageResult = deviceService.getDevicePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, IotDeviceRespVO.class)); + } + + @GetMapping("/count") + @Operation(summary = "获得设备数量") + @Parameter(name = "productId", description = "产品编号", example = "1") + @PreAuthorize("@ss.hasPermission('iot:device:query')") + public CommonResult getDeviceCount(@RequestParam("productId") Long productId) { + return success(deviceService.getDeviceCountByProductId(productId)); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDevicePageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDevicePageReqVO.java new file mode 100644 index 000000000..26bdaca05 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDevicePageReqVO.java @@ -0,0 +1,87 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum; +import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - IoT 设备分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class IotDevicePageReqVO extends PageParam { + + // TODO @芋艿:需要去掉一些多余的字段; + + @Schema(description = "设备唯一标识符", example = "24602") + private String deviceKey; + + @Schema(description = "设备名称", example = "王五") + private String deviceName; + + @Schema(description = "备注名称", example = "张三") + private String nickname; + + @Schema(description = "产品编号", example = "26202") + private Long productId; + + @Schema(description = "产品标识") + private String productKey; + + @Schema(description = "设备类型", example = "1") + @InEnum(IotProductDeviceTypeEnum.class) + private Integer deviceType; + + @Schema(description = "网关设备 ID", example = "16380") + private Long gatewayId; + + @Schema(description = "设备状态", example = "1") + @InEnum(IotDeviceStatusEnum.class) + private Integer status; + + @Schema(description = "设备状态最后更新时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] statusLastUpdateTime; + + @Schema(description = "最后上线时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] lastOnlineTime; + + @Schema(description = "最后离线时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] lastOfflineTime; + + @Schema(description = "设备激活时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] activeTime; + + @Schema(description = "设备密钥,用于设备认证,需安全存储") + private String deviceSecret; + + @Schema(description = "MQTT 客户端 ID", example = "24602") + private String mqttClientId; + + @Schema(description = "MQTT 用户名", example = "芋艿") + private String mqttUsername; + + @Schema(description = "MQTT 密码") + private String mqttPassword; + + @Schema(description = "认证类型(如一机一密、动态注册)", example = "2") + private String authType; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDeviceRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDeviceRespVO.java new file mode 100644 index 000000000..488f6b907 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDeviceRespVO.java @@ -0,0 +1,90 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IoT 设备 Response VO") +@Data +@ExcelIgnoreUnannotated +public class IotDeviceRespVO { + + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177") + private Long id; + + @Schema(description = "设备唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("设备唯一标识符") + private String deviceKey; + + @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @ExcelProperty("设备名称备") + private String deviceName; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202") + @ExcelProperty("产品编号") + private Long productId; + + @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品 Key") + private String productKey; + + @Schema(description = "设备类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("设备类型") + private Integer deviceType; + + @Schema(description = "设备备注名称", example = "张三") + @ExcelProperty("设备备注名称") + private String nickname; + + @Schema(description = "网关设备 ID", example = "16380") + private Long gatewayId; + + @Schema(description = "设备状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("设备状态") + private Integer status; + + @Schema(description = "设备状态最后更新时间") + @ExcelProperty("设备状态最后更新时间") + private LocalDateTime statusLastUpdateTime; + + @Schema(description = "最后上线时间") + @ExcelProperty("最后上线时间") + private LocalDateTime lastOnlineTime; + + @Schema(description = "最后离线时间") + @ExcelProperty("最后离线时间") + private LocalDateTime lastOfflineTime; + + @Schema(description = "设备激活时间") + @ExcelProperty("设备激活时间") + private LocalDateTime activeTime; + + @Schema(description = "设备密钥,用于设备认证") + @ExcelProperty("设备密钥") + private String deviceSecret; + + @Schema(description = "MQTT 客户端 ID", example = "24602") + @ExcelProperty("MQTT 客户端 ID") + private String mqttClientId; + + @Schema(description = "MQTT 用户名", example = "芋艿") + @ExcelProperty("MQTT 用户名") + private String mqttUsername; + + @Schema(description = "MQTT 密码") + @ExcelProperty("MQTT 密码") + private String mqttPassword; + + @Schema(description = "认证类型(如一机一密、动态注册)", example = "2") + @ExcelProperty("认证类型(如一机一密、动态注册)") + private String authType; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDeviceSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDeviceSaveReqVO.java new file mode 100644 index 000000000..620e5310f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDeviceSaveReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - IoT 设备新增/修改 Request VO") +@Data +public class IotDeviceSaveReqVO { + + @Schema(description = "设备编号", example = "177") + private Long id; + + @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + private String deviceName; + + @Schema(description = "备注名称", example = "张三") + private String nickname; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202") + private Long productId; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDeviceStatusUpdateReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDeviceStatusUpdateReqVO.java new file mode 100644 index 000000000..a91a58690 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/IotDeviceStatusUpdateReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - IoT 设备状态更新 Request VO") +@Data +public class IotDeviceStatusUpdateReqVO { + + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "设备编号不能为空") + private Long id; + + @Schema(description = "设备状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "设备状态不能为空") + @InEnum(IotDeviceStatusEnum.class) + private Integer status; +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java new file mode 100644 index 000000000..5b0ecb27a --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java @@ -0,0 +1,95 @@ +package cn.iocoder.yudao.module.iot.controller.admin.product; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductRespVO; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductSaveReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductSimpleRespVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; +import cn.iocoder.yudao.module.iot.service.product.IotProductService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - IoT 产品") +@RestController +@RequestMapping("/iot/product") +@Validated +public class IotProductController { + + @Resource + private IotProductService productService; + + @PostMapping("/create") + @Operation(summary = "创建产品") + @PreAuthorize("@ss.hasPermission('iot:product:create')") + public CommonResult createProduct(@Valid @RequestBody IotProductSaveReqVO createReqVO) { + return success(productService.createProduct(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新产品") + @PreAuthorize("@ss.hasPermission('iot:product:update')") + public CommonResult updateProduct(@Valid @RequestBody IotProductSaveReqVO updateReqVO) { + productService.updateProduct(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新产品状态") + @Parameter(name = "id", description = "编号", required = true) + @Parameter(name = "status", description = "状态", required = true) + @PreAuthorize("@ss.hasPermission('iot:product:update')") + public CommonResult updateProductStatus(@RequestParam("id") Long id, + @RequestParam("status") Integer status) { + productService.updateProductStatus(id, status); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除产品") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('iot:product:delete')") + public CommonResult deleteProduct(@RequestParam("id") Long id) { + productService.deleteProduct(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得产品") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('iot:product:query')") + public CommonResult getProduct(@RequestParam("id") Long id) { + IotProductDO product = productService.getProduct(id); + return success(BeanUtils.toBean(product, IotProductRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得产品分页") + @PreAuthorize("@ss.hasPermission('iot:product:query')") + public CommonResult> getProductPage(@Valid IotProductPageReqVO pageReqVO) { + PageResult pageResult = productService.getProductPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, IotProductRespVO.class)); + } + + // TODO @haohao:改成 simple-list 哈 + @GetMapping("/list-all-simple") + @Operation(summary = "获得所有产品列表") + @PreAuthorize("@ss.hasPermission('iot:product:query')") + public CommonResult> listAllSimpleProducts() { + List list = productService.getProductList(); + return success(BeanUtils.toBean(list, IotProductSimpleRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/IotProductPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/IotProductPageReqVO.java new file mode 100644 index 000000000..3437f563f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/IotProductPageReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.iot.controller.admin.product.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - IoT 产品分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class IotProductPageReqVO extends PageParam { + + @Schema(description = "产品名称", example = "李四") + private String name; + + @Schema(description = "产品标识") + private String productKey; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/IotProductRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/IotProductRespVO.java new file mode 100644 index 000000000..0958b3e84 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/IotProductRespVO.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.iot.controller.admin.product.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IoT 产品 Response VO") +@Data +@ExcelIgnoreUnannotated +public class IotProductRespVO { + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26087") + @ExcelProperty("产品编号") + private Long id; + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @ExcelProperty("产品名称") + private String name; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品标识") + private String productKey; + + @Schema(description = "接入网关协议", example = "2") + @ExcelProperty("接入网关协议") + private Integer protocolType; + + @Schema(description = "协议编号(脚本解析 id)", requiredMode = Schema.RequiredMode.REQUIRED, example = "13177") + @ExcelProperty("协议编号(脚本解析 id)") + private Long protocolId; + + @Schema(description = "产品所属品类标识符", example = "14237") + @ExcelProperty("产品所属品类标识符") + private Long categoryId; + + @Schema(description = "产品描述", example = "你猜") + @ExcelProperty("产品描述") + private String description; + + @Schema(description = "数据校验级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("数据校验级别") + private Integer validateType; + + @Schema(description = "产品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("产品状态") + private Integer status; + + @Schema(description = "设备类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("设备类型") + private Integer deviceType; + + @Schema(description = "联网方式", example = "2") + @ExcelProperty("联网方式") + private Integer netType; + + @Schema(description = "数据格式") + @ExcelProperty("数据格式") + private Integer dataFormat; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/IotProductSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/IotProductSaveReqVO.java new file mode 100644 index 000000000..254b6b9da --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/IotProductSaveReqVO.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.module.iot.controller.admin.product.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.iot.enums.product.*; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - IoT 产品新增/修改 Request VO") +@Data +public class IotProductSaveReqVO { + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.AUTO, example = "1") + private Long id; + + @Schema(description = "产品Key", requiredMode = Schema.RequiredMode.AUTO, example = "12345abc") + private String productKey; + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "温湿度") + @NotEmpty(message = "产品名称不能为空") + private String name; + + @Schema(description = "设备类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @InEnum(value = IotProductDeviceTypeEnum.class, message = "设备类型必须是 {value}") + @NotNull(message = "设备类型不能为空") + private Integer deviceType; + + @Schema(description = "联网方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @InEnum(value = IotNetTypeEnum.class, message = "联网方式必须是 {value}") + private Integer netType; + + @Schema(description = "接入网关协议", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @InEnum(value = IotProtocolTypeEnum.class, message = "接入网关协议必须是 {value}") + private Integer protocolType; + + @Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @InEnum(value = IotDataFormatEnum.class, message = "数据格式必须是 {value}") + @NotNull(message = "数据格式不能为空") + private Integer dataFormat; + + @Schema(description = "数据校验级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @InEnum(value = IotValidateTypeEnum.class, message = "数据校验级别必须是 {value}") + @NotNull(message = "数据校验级别不能为空") + private Integer validateType; + + @Schema(description = "产品描述", example = "描述") + private String description; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/IotProductSimpleRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/IotProductSimpleRespVO.java new file mode 100644 index 000000000..83855eaaf --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/IotProductSimpleRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.iot.controller.admin.product.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - IoT 产品 Response VO") +@Data +public class IotProductSimpleRespVO { + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26087") + private Long id; + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + private String name; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/IotThinkModelFunctionController.http b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/IotThinkModelFunctionController.http new file mode 100644 index 000000000..56464dd80 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/IotThinkModelFunctionController.http @@ -0,0 +1,112 @@ +### 请求 /iot/think-model-function/create 接口 => 成功 +POST {{baseUrl}}/iot/think-model-function/create +Content-Type: application/json +tenant-id: {{adminTenentId}} +Authorization: Bearer {{token}} + +{ + "productId": 1001, + "productKey": "smart-sensor-001", + "identifier": "Temperature", + "name": "温度", + "description": "当前温度值", + "type": 1, + "property": { + "identifier": "Temperature", + "name": "温度", + "accessMode": "r", + "required": true, + "dataType": { + "type": "float", + "specs": { + "min": -10.0, + "max": 100.0, + "step": 0.1, + "unit": "℃" + } + }, + "description": "当前温度值" + } +} + +### 请求 /iot/think-model-function/create 接口 => 成功 +POST {{baseUrl}}/iot/think-model-function/create +Content-Type: application/json +tenant-id: {{adminTenentId}} +Authorization: Bearer {{token}} + +{ + "productId": 1001, + "productKey": "smart-sensor-001", + "identifier": "Humidity", + "name": "湿度", + "description": "当前湿度值", + "type": 1, + "property": { + "identifier": "Humidity", + "name": "湿度", + "accessMode": "r", + "required": true, + "dataType": { + "type": "float", + "specs": { + "min": 0.0, + "max": 100.0, + "step": 0.1, + "unit": "%" + } + }, + "description": "当前湿度值" + } +} + + + + +### 请求 /iot/think-model-function/update 接口 => 成功 +PUT {{baseUrl}}/iot/think-model-function/update +Content-Type: application/json +tenant-id: {{adminTenentId}} +Authorization: Bearer {{token}} + +{ + "id": 11, + "productId": 1001, + "productKey": "smart-sensor-001", + "identifier": "Temperature", + "name": "温度", + "description": "当前温度值", + "type": 1, + "property": { + "identifier": "Temperature", + "name": "温度", + "accessMode": "r", + "required": true, + "dataType": { + "type": "float", + "specs": { + "min": -111.0, + "max": 222.0, + "step": 0.1, + "unit": "℃" + } + }, + "description": "当前温度值" + } +} + +### 请求 /iot/think-model-function/delete 接口 => 成功 +DELETE {{baseUrl}}/iot/think-model-function/delete?id=7 +tenant-id: {{adminTenentId}} +Authorization: Bearer {{token}} + +### 请求 /iot/think-model-function/get 接口 => 成功 +GET {{baseUrl}}/iot/think-model-function/get?id=10 +tenant-id: {{adminTenentId}} +Authorization: Bearer {{token}} + + +### 请求 /iot/think-model-function/list-by-product-id 接口 => 成功 +GET {{baseUrl}}/iot/think-model-function/list-by-product-id?productId=1001 +tenant-id: {{adminTenentId}} +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/IotThinkModelFunctionController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/IotThinkModelFunctionController.java new file mode 100644 index 000000000..4f48f3628 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/IotThinkModelFunctionController.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionRespVO; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionSaveReqVO; +import cn.iocoder.yudao.module.iot.convert.thinkmodelfunction.IotThinkModelFunctionConvert; +import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO; +import cn.iocoder.yudao.module.iot.service.thinkmodelfunction.IotThinkModelFunctionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - IoT 产品物模型") +@RestController +@RequestMapping("/iot/think-model-function") +@Validated +public class IotThinkModelFunctionController { + + @Resource + private IotThinkModelFunctionService thinkModelFunctionService; + + @PostMapping("/create") + @Operation(summary = "创建产品物模型") + @PreAuthorize("@ss.hasPermission('iot:think-model-function:create')") + public CommonResult createThinkModelFunction(@Valid @RequestBody IotThinkModelFunctionSaveReqVO createReqVO) { + return success(thinkModelFunctionService.createThinkModelFunction(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新产品物模型") + @PreAuthorize("@ss.hasPermission('iot:think-model-function:update')") + public CommonResult updateThinkModelFunction(@Valid @RequestBody IotThinkModelFunctionSaveReqVO updateReqVO) { + thinkModelFunctionService.updateThinkModelFunction(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除产品物模型") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('iot:think-model-function:delete')") + public CommonResult deleteThinkModelFunction(@RequestParam("id") Long id) { + thinkModelFunctionService.deleteThinkModelFunction(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得产品物模型") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('iot:think-model-function:query')") + public CommonResult getThinkModelFunction(@RequestParam("id") Long id) { + IotThinkModelFunctionDO function = thinkModelFunctionService.getThinkModelFunction(id); + return success(IotThinkModelFunctionConvert.INSTANCE.convert(function)); + } + + @GetMapping("/list-by-product-id") + @Operation(summary = "获得产品物模型") + @Parameter(name = "productId", description = "产品ID", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('iot:think-model-function:query')") + public CommonResult> getThinkModelFunctionListByProductId(@RequestParam("productId") Long productId) { + List list = thinkModelFunctionService.getThinkModelFunctionListByProductId(productId); + return success(IotThinkModelFunctionConvert.INSTANCE.convertList(list)); + } + + @GetMapping("/page") + @Operation(summary = "获得产品物模型分页") + @PreAuthorize("@ss.hasPermission('iot:think-model-function:query')") + public CommonResult> getThinkModelFunctionPage(@Valid IotThinkModelFunctionPageReqVO pageReqVO) { + PageResult pageResult = thinkModelFunctionService.getThinkModelFunctionPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, IotThinkModelFunctionRespVO.class)); + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/ThingModelEvent.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/ThingModelEvent.java new file mode 100644 index 000000000..d7fa68758 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/ThingModelEvent.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel; + +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType.ThingModelArgument; +import lombok.Data; +import java.util.List; + +@Data +public class ThingModelEvent { + + /** + * 事件标识符 + */ + private String identifier; + /** + * 事件名称 + */ + private String name; + /** + * 事件描述 + */ + private String description; + + /** + * 事件类型 + * + * "info"、"alert"、"error" + */ + private String type; + private List outputData; + private String method; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/ThingModelProperty.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/ThingModelProperty.java new file mode 100644 index 000000000..025d37e76 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/ThingModelProperty.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel; + +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType.ThingModelDataType; +import lombok.Data; + +@Data +public class ThingModelProperty { + + /** + * 属性标识符 + */ + private String identifier; + /** + * 属性名称 + */ + private String name; + /** + * 属性描述 + */ + private String description; + + private String accessMode; // "rw"、"r"、"w" + private Boolean required; + // TODO @haohao:这个是不是 dataSpecs 和 dataSpecsList?https://help.aliyun.com/zh/iot/developer-reference/api-a99t11 + private ThingModelDataType dataType; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/ThingModelService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/ThingModelService.java new file mode 100644 index 000000000..d97e05e9c --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/ThingModelService.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel; + +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType.ThingModelArgument; +import lombok.Data; +import java.util.List; + +@Data +public class ThingModelService { + + /** + * 服务标识符 + */ + private String identifier; + /** + * 服务名称 + */ + private String name; + /** + * 服务描述 + */ + private String description; + + /** + * 调用类型 + * + * "sync"、"async" + */ + private String callType; + private List inputData; + private List outputData; + private String method; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelArgument.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelArgument.java new file mode 100644 index 000000000..2be24004e --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelArgument.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType; + +import lombok.Data; + +@Data +public class ThingModelArgument { + + private String identifier; + private String name; + private ThingModelDataType dataType; + /** + * 用于区分输入或输出参数,"input" 或 "output" + */ + private String direction; + private String description; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelArraySpecs.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelArraySpecs.java new file mode 100644 index 000000000..c3faf6161 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelArraySpecs.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType; + +import lombok.Data; + +@Data +public class ThingModelArraySpecs { + + /** + * 数组长度 + */ + private int size; + /** + * 数组元素的类型 + */ + private ThingModelDataType item; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelArrayType.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelArrayType.java new file mode 100644 index 000000000..bab87be0a --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelArrayType.java @@ -0,0 +1,12 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType; + +import lombok.Data; + +// TODO @haohao:这个是不是和别的类,不太统一哈 +@Data +public class ThingModelArrayType extends ThingModelDataType { + + private ThingModelArraySpecs specs; + +} + diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelBoolType.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelBoolType.java new file mode 100644 index 000000000..b8ca64195 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelBoolType.java @@ -0,0 +1,12 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class ThingModelBoolType extends ThingModelDataType { + + // Bool 类型一般不需要额外的 specs + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelDataType.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelDataType.java new file mode 100644 index 000000000..ec5f04bbb --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelDataType.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.Data; + +@Data +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type(value = ThingModelIntType.class, name = "int"), + @JsonSubTypes.Type(value = ThingModelFloatType.class, name = "float"), + @JsonSubTypes.Type(value = ThingModelDoubleType.class, name = "double"), + @JsonSubTypes.Type(value = ThingModelTextType.class, name = "text"), + @JsonSubTypes.Type(value = ThingModelDateType.class, name = "date"), + @JsonSubTypes.Type(value = ThingModelBoolType.class, name = "bool"), + @JsonSubTypes.Type(value = ThingModelEnumType.class, name = "enum"), + @JsonSubTypes.Type(value = ThingModelStructType.class, name = "struct"), + @JsonSubTypes.Type(value = ThingModelArrayType.class, name = "array") +}) +public abstract class ThingModelDataType { + + private String type; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelDateType.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelDateType.java new file mode 100644 index 000000000..854229339 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelDateType.java @@ -0,0 +1,10 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType; + +import lombok.Data; + +@Data +public class ThingModelDateType extends ThingModelDataType { + + // Date 类型一般不需要额外的 specs + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelDoubleType.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelDoubleType.java new file mode 100644 index 000000000..e5f3ad268 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelDoubleType.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType; + +import lombok.Data; + +@Data +public class ThingModelDoubleType extends ThingModelDataType { + private ThingModelDoubleSpecs specs; +} + +@Data +class ThingModelDoubleSpecs { + + private Double min; + private Double max; + private Double step; + private String unit; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelEnumType.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelEnumType.java new file mode 100644 index 000000000..3dcb068e9 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelEnumType.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType; + +import lombok.Data; + +import java.util.Map; + +@Data +public class ThingModelEnumType extends ThingModelDataType { + + /** + * 枚举值和描述的键值对 + */ + private Map specs; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelFloatType.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelFloatType.java new file mode 100644 index 000000000..27926fa49 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelFloatType.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class ThingModelFloatType extends ThingModelDataType { + private ThingModelFloatSpecs specs; +} + +@Data +class ThingModelFloatSpecs { + + private Float min; + private Float max; + private Float step; + private String unit; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelIntType.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelIntType.java new file mode 100644 index 000000000..a126eb749 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelIntType.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType; + +import lombok.Data; + +@Data +public class ThingModelIntType extends ThingModelDataType { + private ThingModelIntSpecs specs; +} + +@Data +class ThingModelIntSpecs { + + private Integer min; + private Integer max; + private Integer step; + private String unit; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelStructField.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelStructField.java new file mode 100644 index 000000000..5e079f22b --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelStructField.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType; + +import lombok.Data; + +@Data +public class ThingModelStructField { + + private String identifier; + private String name; + private ThingModelDataType dataType; + private String description; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelStructType.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelStructType.java new file mode 100644 index 000000000..f0996513c --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelStructType.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType; + +import lombok.Data; + +import java.util.List; + +@Data +public class ThingModelStructType extends ThingModelDataType { + + private List specs; + +} + + diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelTextType.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelTextType.java new file mode 100644 index 000000000..16d1e402e --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/dataType/ThingModelTextType.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType; + +import lombok.Data; + +@Data +public class ThingModelTextType extends ThingModelDataType { + + private ThingModelTextSpecs specs; + +} + +@Data +class ThingModelTextSpecs { + + /** + * 最大长度 + */ + private Integer length; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/vo/IotThinkModelFunctionPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/vo/IotThinkModelFunctionPageReqVO.java new file mode 100644 index 000000000..8a590d429 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/vo/IotThinkModelFunctionPageReqVO.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - IoT 产品物模型分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class IotThinkModelFunctionPageReqVO extends PageParam { + + @Schema(description = "功能标识") + private String identifier; + + @Schema(description = "功能名称", example = "张三") + private String name; + + @Schema(description = "功能类型", example = "1") + @InEnum(IotProductFunctionTypeEnum.class) + private Integer type; + + @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "产品ID不能为空") + private Long productId; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/vo/IotThinkModelFunctionRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/vo/IotThinkModelFunctionRespVO.java new file mode 100644 index 000000000..9ef3f58d8 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/vo/IotThinkModelFunctionRespVO.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo; + +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelEvent; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelProperty; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelService; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - IoT 产品物模型 Response VO") +@Data +@ExcelIgnoreUnannotated +public class IotThinkModelFunctionRespVO { + + @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "21816") + @ExcelProperty("产品ID") + private Long id; + + @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED) + private Long productId; + + @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品标识") + private String productKey; + + @Schema(description = "功能标识", requiredMode = Schema.RequiredMode.REQUIRED) + private String identifier; + + @Schema(description = "功能名称", requiredMode = Schema.RequiredMode.REQUIRED) + private String name; + + @Schema(description = "功能描述", requiredMode = Schema.RequiredMode.REQUIRED) + private String description; + + @Schema(description = "功能类型", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer type; + + @Schema(description = "属性", requiredMode = Schema.RequiredMode.REQUIRED) + private ThingModelProperty property; + + @Schema(description = "服务", requiredMode = Schema.RequiredMode.REQUIRED) + private ThingModelEvent event; + + @Schema(description = "事件", requiredMode = Schema.RequiredMode.REQUIRED) + private ThingModelService service; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/vo/IotThinkModelFunctionSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/vo/IotThinkModelFunctionSaveReqVO.java new file mode 100644 index 000000000..7d51ce504 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/vo/IotThinkModelFunctionSaveReqVO.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelEvent; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelProperty; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelService; +import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - IoT 产品物模型新增/修改 Request VO") +@Data +public class IotThinkModelFunctionSaveReqVO { + + @Schema(description = "编号", example = "1") + private Long id; + + @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "产品ID不能为空") + private Long productId; + + @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "产品标识不能为空") + private String productKey; + + @Schema(description = "功能标识", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "功能标识不能为空") + private String identifier; + + @Schema(description = "功能名称", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "功能名称不能为空") + private String name; + + @Schema(description = "功能描述", requiredMode = Schema.RequiredMode.REQUIRED) + private String description; + + @Schema(description = "功能类型", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "功能类型不能为空") + @InEnum(IotProductFunctionTypeEnum.class) + private Integer type; + + @Schema(description = "属性", requiredMode = Schema.RequiredMode.REQUIRED) + private ThingModelProperty property; + + @Schema(description = "服务", requiredMode = Schema.RequiredMode.REQUIRED) + private ThingModelService service; + + @Schema(description = "事件", requiredMode = Schema.RequiredMode.REQUIRED) + private ThingModelEvent event; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/package-info.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/package-info.java new file mode 100644 index 000000000..5d2990e1b --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 RESTful API 给前端: + * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 + * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 + */ +package cn.iocoder.yudao.module.iot.controller; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/package-info.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/package-info.java new file mode 100644 index 000000000..c196c25c3 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.module.iot.convert; \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/thinkmodelfunction/IotThinkModelFunctionConvert.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/thinkmodelfunction/IotThinkModelFunctionConvert.java new file mode 100644 index 000000000..764d4c030 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/thinkmodelfunction/IotThinkModelFunctionConvert.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.iot.convert.thinkmodelfunction; + +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelEvent; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelProperty; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelService; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionRespVO; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO; +import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Objects; + +@Mapper +public interface IotThinkModelFunctionConvert { + + IotThinkModelFunctionConvert INSTANCE = Mappers.getMapper(IotThinkModelFunctionConvert.class); + + // 将 SaveReqVO 转换为 DO + @Mapping(target = "property", expression = "java(convertToProperty(bean))") + @Mapping(target = "event", expression = "java(convertToEvent(bean))") + @Mapping(target = "service", expression = "java(convertToService(bean))") + IotThinkModelFunctionDO convert(IotThinkModelFunctionSaveReqVO bean); + + default ThingModelProperty convertToProperty(IotThinkModelFunctionSaveReqVO bean) { + if (Objects.equals(bean.getType(), IotProductFunctionTypeEnum.PROPERTY.getType())) { + return bean.getProperty(); + } + return null; + } + + default ThingModelEvent convertToEvent(IotThinkModelFunctionSaveReqVO bean) { + if (Objects.equals(bean.getType(), IotProductFunctionTypeEnum.EVENT.getType())) { + return bean.getEvent(); + } + return null; + } + + default ThingModelService convertToService(IotThinkModelFunctionSaveReqVO bean) { + if (Objects.equals(bean.getType(), IotProductFunctionTypeEnum.SERVICE.getType())) { + return bean.getService(); + } + return null; + } + + // 将 DO 转换为 RespVO + @Mapping(target = "property", source = "property") + @Mapping(target = "event", source = "event") + @Mapping(target = "service", source = "service") + IotThinkModelFunctionRespVO convert(IotThinkModelFunctionDO bean); + + // 批量转换 + List convertList(List list); +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java new file mode 100644 index 000000000..d3f6547a1 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java @@ -0,0 +1,152 @@ +package cn.iocoder.yudao.module.iot.dal.dataobject.device; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; +import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * IoT 设备 DO + * + * @author haohao + */ +@TableName("iot_device") +@KeySequence("iot_device_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class IotDeviceDO extends BaseDO { + + /** + * 设备 ID,主键,自增 + */ + @TableId + private Long id; + /** + * 设备唯一标识符,全局唯一,用于识别设备 + */ + private String deviceKey; + /** + * 设备名称,在产品内唯一,用于标识设备 + */ + private String deviceName; + /** + * 设备备注名称 + */ + private String nickname; + /** + * 设备序列号 + */ + private String serialNumber; + + /** + * 产品编号 + *

+ * 关联 {@link IotProductDO#getId()} + */ + private Long productId; + /** + * 产品标识 + *

+ * 冗余 {@link IotProductDO#getProductKey()} + */ + private String productKey; + /** + * 设备类型 + *

+ * 冗余 {@link IotProductDO#getDeviceType()} + */ + private Integer deviceType; + + /** + * 设备状态 + *

+ * 枚举 {@link IotDeviceStatusEnum} + */ + private Integer status; + /** + * 网关设备编号 + *

+ * 子设备需要关联的网关设备 ID + *

+ * 关联 {@link IotDeviceDO#getId()} + */ + private Long gatewayId; + + /** + * 设备状态最后更新时间 + */ + private LocalDateTime statusLastUpdateTime; + /** + * 最后上线时间 + */ + private LocalDateTime lastOnlineTime; + /** + * 最后离线时间 + */ + private LocalDateTime lastOfflineTime; + /** + * 设备激活时间 + */ + private LocalDateTime activeTime; + + /** + * 设备的 IP 地址 + */ + private String ip; + /** + * 设备的固件版本 + */ + private String firmwareVersion; + + /** + * 设备密钥,用于设备认证,需安全存储 + */ + private String deviceSecret; + /** + * MQTT 客户端 ID + */ + private String mqttClientId; + /** + * MQTT 用户名 + */ + private String mqttUsername; + /** + * MQTT 密码 + */ + private String mqttPassword; + /** + * 认证类型(如一机一密、动态注册) + */ + // TODO @haohao:是不是要枚举哈 + private String authType; + + /** + * 设备位置的纬度 + */ + private BigDecimal latitude; + /** + * 设备位置的经度 + */ + private BigDecimal longitude; + /** + * 地区编码 + *

+ * 关联 Area 的 id + */ + private Integer areaId; + /** + * 设备详细地址 + */ + private String address; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java new file mode 100644 index 000000000..eef466eda --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java @@ -0,0 +1,93 @@ +package cn.iocoder.yudao.module.iot.dal.dataobject.product; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * IoT 产品 DO + * + * @author ahh + */ +@TableName("iot_product") +@KeySequence("iot_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class IotProductDO extends BaseDO { + + /** + * 产品ID + */ + @TableId + private Long id; + /** + * 产品名称 + */ + private String name; + // TODO @haohao:这个字段,要不改成 identifier,和阿里云更统一些 + /** + * 产品标识 + */ + private String productKey; + /** + * 产品所属品类编号 + *

+ * TODO 外键:后续加 + */ + private Long categoryId; + /** + * 产品描述 + */ + private String description; + + /** + * 产品状态 + *

+ * 枚举 {@link cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum} + */ + private Integer status; + /** + * 设备类型 + *

+ * 枚举 {@link cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum} + */ + private Integer deviceType; + /** + * 联网方式 + *

+ * 枚举 {@link cn.iocoder.yudao.module.iot.enums.product.IotNetTypeEnum} + */ + private Integer netType; + + /** + * 接入网关协议 + *

+ * 枚举 {@link cn.iocoder.yudao.module.iot.enums.product.IotProtocolTypeEnum} + */ + private Integer protocolType; + /** + * 协议编号 + *

+ * TODO 外键:后续加 + */ + private Long protocolId; + /** + * 数据格式 + *

+ * 枚举 {@link cn.iocoder.yudao.module.iot.enums.product.IotDataFormatEnum} + */ + private Integer dataFormat; + /** + * 数据校验级别 + *

+ * 枚举 {@link cn.iocoder.yudao.module.iot.enums.product.IotValidateTypeEnum} + */ + private Integer validateType; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thinkmodelfunction/IotThinkModelFunctionDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thinkmodelfunction/IotThinkModelFunctionDO.java new file mode 100644 index 000000000..02b2f9707 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thinkmodelfunction/IotThinkModelFunctionDO.java @@ -0,0 +1,91 @@ +package cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelEvent; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelProperty; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelService; +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; +import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * IoT 产品物模型功能 DO + *

+ * 每个 {@link IotProductDO} 和 {@link IotThinkModelFunctionDO} 是“一对多”的关系,它的每个属性、事件、服务都对应一条记录 + * + * @author 芋道源码 + */ +@TableName(value = "iot_think_model_function", autoResultMap = true) +@KeySequence("iot_think_model_function_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class IotThinkModelFunctionDO extends BaseDO { + + /** + * 物模型功能编号 + */ + @TableId + private Long id; + + /** + * 功能标识 + */ + private String identifier; + /** + * 功能名称 + */ + private String name; + /** + * 功能描述 + */ + private String description; + + /** + * 产品标识 + *

+ * 关联 {@link IotProductDO#getId()} + */ + private Long productId; + /** + * 产品标识 + *

+ * 关联 {@link IotProductDO#getProductKey()} + */ + private String productKey; + + /** + * 功能类型 + *

+ * 枚举 {@link IotProductFunctionTypeEnum} + */ + private Integer type; + + /** + * 属性 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private ThingModelProperty property; + + /** + * 事件 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private ThingModelEvent event; + + /** + * 服务 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private ThingModelService service; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java new file mode 100644 index 000000000..0e5552f83 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.iot.dal.mysql.device; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDevicePageReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * IoT 设备 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface IotDeviceMapper extends BaseMapperX { + + default PageResult selectPage(IotDevicePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(IotDeviceDO::getDeviceKey, reqVO.getDeviceKey()) + .likeIfPresent(IotDeviceDO::getDeviceName, reqVO.getDeviceName()) + .eqIfPresent(IotDeviceDO::getProductId, reqVO.getProductId()) + .eqIfPresent(IotDeviceDO::getProductKey, reqVO.getProductKey()) + .eqIfPresent(IotDeviceDO::getDeviceType, reqVO.getDeviceType()) + .likeIfPresent(IotDeviceDO::getNickname, reqVO.getNickname()) + .eqIfPresent(IotDeviceDO::getGatewayId, reqVO.getGatewayId()) + .eqIfPresent(IotDeviceDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(IotDeviceDO::getStatusLastUpdateTime, reqVO.getStatusLastUpdateTime()) + .betweenIfPresent(IotDeviceDO::getLastOnlineTime, reqVO.getLastOnlineTime()) + .betweenIfPresent(IotDeviceDO::getLastOfflineTime, reqVO.getLastOfflineTime()) + .betweenIfPresent(IotDeviceDO::getActiveTime, reqVO.getActiveTime()) + .eqIfPresent(IotDeviceDO::getDeviceSecret, reqVO.getDeviceSecret()) + .eqIfPresent(IotDeviceDO::getMqttClientId, reqVO.getMqttClientId()) + .likeIfPresent(IotDeviceDO::getMqttUsername, reqVO.getMqttUsername()) + .eqIfPresent(IotDeviceDO::getMqttPassword, reqVO.getMqttPassword()) + .eqIfPresent(IotDeviceDO::getAuthType, reqVO.getAuthType()) + .betweenIfPresent(IotDeviceDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(IotDeviceDO::getId)); + } + + default IotDeviceDO selectByProductKeyAndDeviceName(String productKey, String deviceName) { + return selectOne(IotDeviceDO::getProductKey, productKey, + IotDeviceDO::getDeviceName, deviceName); + } + + default long selectCountByGatewayId(Long id) { + return selectCount(IotDeviceDO::getGatewayId, id); + } + + default Long selectCountByProductId(Long productId) { + return selectCount(IotDeviceDO::getProductId, productId); + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java new file mode 100644 index 000000000..0341e2492 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.iot.dal.mysql.product; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductPageReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * IoT 产品 Mapper + * + * @author ahh + */ +@Mapper +public interface IotProductMapper extends BaseMapperX { + + default PageResult selectPage(IotProductPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(IotProductDO::getName, reqVO.getName()) + .likeIfPresent(IotProductDO::getProductKey, reqVO.getProductKey()) + .orderByDesc(IotProductDO::getId)); + } + + default IotProductDO selectByProductKey(String productKey) { + return selectOne(new LambdaQueryWrapperX().eq(IotProductDO::getProductKey, productKey)); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/thinkmodelfunction/IotThinkModelFunctionMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/thinkmodelfunction/IotThinkModelFunctionMapper.java new file mode 100644 index 000000000..e8b96e022 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/thinkmodelfunction/IotThinkModelFunctionMapper.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.iot.dal.mysql.thinkmodelfunction; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionPageReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * IoT 产品物模型 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface IotThinkModelFunctionMapper extends BaseMapperX { + + default PageResult selectPage(IotThinkModelFunctionPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(IotThinkModelFunctionDO::getIdentifier, reqVO.getIdentifier()) + .likeIfPresent(IotThinkModelFunctionDO::getName, reqVO.getName()) + .eqIfPresent(IotThinkModelFunctionDO::getType, reqVO.getType()) + .eqIfPresent(IotThinkModelFunctionDO::getProductId, reqVO.getProductId()) + .notIn(IotThinkModelFunctionDO::getIdentifier, "get", "set", "post") + .orderByDesc(IotThinkModelFunctionDO::getId)); + } + + default IotThinkModelFunctionDO selectByProductIdAndIdentifier(Long productId, String identifier) { + return selectOne(IotThinkModelFunctionDO::getProductId, productId, + IotThinkModelFunctionDO::getIdentifier, identifier); + } + + default List selectListByProductId(Long productId) { + return selectList(IotThinkModelFunctionDO::getProductId, productId); + } + + default List selectListByProductIdAndType(Long productId, Integer type) { + return selectList(IotThinkModelFunctionDO::getProductId, productId, + IotThinkModelFunctionDO::getType, type); + } + + default List selectListByProductIdAndIdentifiersAndTypes(Long productId, + List identifiers, + List types){ + return selectList(new LambdaQueryWrapperX() + .eq(IotThinkModelFunctionDO::getProductId, productId) + .in(IotThinkModelFunctionDO::getIdentifier, identifiers) + .in(IotThinkModelFunctionDO::getType, types)); + } + + default IotThinkModelFunctionDO selectByProductIdAndName(Long productId, String name) { + return selectOne(IotThinkModelFunctionDO::getProductId, productId, + IotThinkModelFunctionDO::getName, name); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/callback/EmqxCallback.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/callback/EmqxCallback.java new file mode 100644 index 000000000..b466113f7 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/callback/EmqxCallback.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.iot.emq.callback; + +import cn.iocoder.yudao.module.iot.emq.client.EmqxClient; +import cn.iocoder.yudao.module.iot.emq.service.EmqxService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttCallbackExtended; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +// TODO @芋艿:详细再瞅瞅 +/** + * 用于处理MQTT连接的回调,如连接断开、消息到达、消息发布完成、连接完成等事件。 + * + * @author ahh + */ +@Slf4j +@Component +public class EmqxCallback implements MqttCallbackExtended { + + @Lazy + @Resource + private EmqxService emqxService; + + @Lazy + @Resource + private EmqxClient emqxClient; + + @Override + public void connectionLost(Throwable throwable) { + log.info("MQTT 连接断开", throwable); + } + + @Override + public void messageArrived(String topic, MqttMessage mqttMessage) { + emqxService.subscribeCallback(topic, mqttMessage); + } + + @Override + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { + log.info("消息发送成功: {}", iMqttDeliveryToken.getMessageId()); + } + + @Override + public void connectComplete(boolean reconnect, String serverURI) { + log.info("MQTT 已连接到服务器: {}", serverURI); + emqxService.subscribe(emqxClient.getMqttClient()); + } +} + + + diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/client/EmqxClient.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/client/EmqxClient.java new file mode 100644 index 000000000..de24585b0 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/client/EmqxClient.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.module.iot.emq.client; + +import cn.iocoder.yudao.module.iot.emq.callback.EmqxCallback; +import cn.iocoder.yudao.module.iot.emq.config.MqttConfig; +import jakarta.annotation.Resource; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.springframework.stereotype.Component; + +/** + * MQTT客户端类,负责建立与MQTT服务器的连接,提供发布消息和订阅主题的功能 + * + * @author ahh + */ +@Slf4j +@Data +@Component +public class EmqxClient { + + @Resource + private EmqxCallback emqxCallback; + @Resource + private MqttConfig mqttConfig; + + private MqttClient mqttClient; + + public void connect() { + if (mqttClient == null) { + createMqttClient(); + } + try { + mqttClient.connect(createMqttOptions()); + log.info("MQTT客户端连接成功"); + } catch (MqttException e) { + log.error("MQTT客户端连接失败", e); + } + } + + private void createMqttClient() { + try { + mqttClient = new MqttClient(mqttConfig.getHostUrl(), "yudao" + mqttConfig.getClientId(), new MemoryPersistence()); + mqttClient.setCallback(emqxCallback); + } catch (MqttException e) { + log.error("创建MQTT客户端失败", e); + } + } + + private MqttConnectOptions createMqttOptions() { + MqttConnectOptions options = new MqttConnectOptions(); + options.setUserName(mqttConfig.getUsername()); + options.setPassword(mqttConfig.getPassword().toCharArray()); + options.setConnectionTimeout(mqttConfig.getTimeout()); + options.setKeepAliveInterval(mqttConfig.getKeepalive()); + options.setCleanSession(mqttConfig.isClearSession()); + return options; + } + + public void publish(String topic, String message) { + try { + if (mqttClient == null || !mqttClient.isConnected()) { + connect(); + } + mqttClient.publish(topic, new MqttMessage(message.getBytes())); + log.info("消息已发布到主题: {}", topic); + } catch (MqttException e) { + log.error("消息发布失败", e); + } + } + + public void subscribe(String topic) { + try { + if (mqttClient == null || !mqttClient.isConnected()) { + connect(); + } + mqttClient.subscribe(topic); + log.info("订阅了主题: {}", topic); + } catch (MqttException e) { + log.error("订阅主题失败", e); + } + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/config/MqttConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/config/MqttConfig.java new file mode 100644 index 000000000..9d128903c --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/config/MqttConfig.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.iot.emq.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +// TODO @芋艿:详细再瞅瞅 + +/** + * 配置类,用于读取MQTT连接的配置信息,如用户名、密码、连接地址等 + * + * @author ahh + */ +@Data +@Component +@ConfigurationProperties("iot.emq") +public class MqttConfig { + + /** + * 用户名 + */ + private String username; + /** + * 密码 + */ + private String password; + + /** + * 连接地址 + */ + private String hostUrl; + + /** + * 客户Id + */ + private String clientId; + + /** + * 默认连接话题 + */ + private String defaultTopic; + + /** + * 超时时间 + */ + private int timeout; + + /** + * 保持连接数 + */ + private int keepalive; + + /** + * 是否清除session + */ + private boolean clearSession; + + /** + * 是否共享订阅 + */ + private boolean isShared; + + /** + * 分组共享订阅 + */ + private boolean isSharedGroup; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxService.java new file mode 100644 index 000000000..0d564c39f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxService.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.iot.emq.service; + +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +// TODO @芋艿:在瞅瞅 +/** + * 用于处理MQTT消息的具体业务逻辑,如订阅回调 + * + * @author ahh + */ +public interface EmqxService { + + /** + * 订阅回调 + * + * @param topic 主题 + * @param mqttMessage 消息 + */ + void subscribeCallback(String topic, MqttMessage mqttMessage); + + /** + * 订阅主题 + * + * @param client MQTT 客户端 + */ + void subscribe(MqttClient client); +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java new file mode 100644 index 000000000..0c1a87f7f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.iot.emq.service; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.springframework.stereotype.Service; + +// TODO @芋艿:在瞅瞅 + +/** + * 用于处理MQTT消息的具体业务逻辑,如订阅回调 + * + * @author ahh + */ +@Slf4j +@Service +public class EmqxServiceImpl implements EmqxService { + + // TODO 多线程处理消息 + @Override + public void subscribeCallback(String topic, MqttMessage mqttMessage) { + log.info("收到消息,主题: {}, 内容: {}", topic, new String(mqttMessage.getPayload())); + // 根据不同的主题,处理不同的业务逻辑 + if (topic.contains("/property/post")) { + // 设备上报数据 + } + } + + @Override + public void subscribe(MqttClient client) { + try { + // 订阅默认主题,可以根据需要修改 +// client.subscribe("$share/yudao/+/+/#", 1); + log.info("订阅默认主题成功"); + } catch (Exception e) { + log.error("订阅默认主题失败", e); + } + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/start/EmqxStart.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/start/EmqxStart.java new file mode 100644 index 000000000..0c316b66c --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/start/EmqxStart.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.iot.emq.start; + +import cn.iocoder.yudao.module.iot.emq.client.EmqxClient; +import jakarta.annotation.Resource; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +// TODO @芋艿:在瞅瞅 + +/** + * 用于在应用启动时自动连接MQTT服务器 + * + * @author ahh + */ +@Component +public class EmqxStart implements ApplicationRunner { + + @Resource + private EmqxClient emqxClient; + + @Override + public void run(ApplicationArguments applicationArguments) { + emqxClient.connect(); + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/package-info.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/package-info.java new file mode 100644 index 000000000..234cad870 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 iot 模块的 framework 封装 + * + * @author ahh + */ +package cn.iocoder.yudao.module.iot.framework; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/web/config/IotWebConfiguration.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/web/config/IotWebConfiguration.java new file mode 100644 index 000000000..6b3cc6ae5 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/web/config/IotWebConfiguration.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.iot.framework.web.config; + +import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * iot 模块的 web 组件的 Configuration + * + * @author ahh + */ +@Configuration(proxyBeanMethods = false) +public class IotWebConfiguration { + + /** + * iot 模块的 API 分组 + */ + @Bean + public GroupedOpenApi iotGroupedOpenApi() { + return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("iot"); + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/web/package-info.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/web/package-info.java new file mode 100644 index 000000000..aafcca274 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/web/package-info.java @@ -0,0 +1,4 @@ +/** + * iot 模块的 web 拓展封装 + */ +package cn.iocoder.yudao.module.iot.framework.web; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java new file mode 100644 index 000000000..2ae08bb94 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java @@ -0,0 +1,227 @@ +package cn.iocoder.yudao.module.iot.service.device; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDevicePageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceSaveReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceStatusUpdateReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; +import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper; +import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum; +import cn.iocoder.yudao.module.iot.service.product.IotProductService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.UUID; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; + +/** + * IoT 设备 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class DeviceServiceImpl implements IotDeviceService { + + @Resource + private IotDeviceMapper deviceMapper; + @Resource + private IotProductService productService; + + /** + * 创建 IoT 设备 + * + * @param createReqVO 创建请求 VO + * @return 设备 ID + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Long createDevice(IotDeviceSaveReqVO createReqVO) { + // 1.1 校验产品是否存在 + IotProductDO product = productService.getProduct(createReqVO.getProductId()); + if (product == null) { + throw exception(PRODUCT_NOT_EXISTS); + } + // 1.2 校验设备名称在同一产品下是否唯一 + if (StrUtil.isBlank(createReqVO.getDeviceName())) { + createReqVO.setDeviceName(generateUniqueDeviceName(product.getProductKey())); + } else { + validateDeviceNameUnique(product.getProductKey(), createReqVO.getDeviceName()); + } + + // 2.1 转换 VO 为 DO + IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class) + .setProductKey(product.getProductKey()) + .setDeviceType(product.getDeviceType()); + // 2.2 生成并设置必要的字段 + device.setDeviceKey(generateUniqueDeviceKey()); + device.setDeviceSecret(generateDeviceSecret()); + device.setMqttClientId(generateMqttClientId()); + device.setMqttUsername(generateMqttUsername(device.getDeviceName(), device.getProductKey())); + device.setMqttPassword(generateMqttPassword()); + // 2.3 设置设备状态为未激活 + device.setStatus(IotDeviceStatusEnum.INACTIVE.getStatus()); + device.setStatusLastUpdateTime(LocalDateTime.now()); + // 2.4 插入到数据库 + deviceMapper.insert(device); + return device.getId(); + } + + /** + * 校验设备名称在同一产品下是否唯一 + * + * @param productKey 产品 Key + * @param deviceName 设备名称 + */ + private void validateDeviceNameUnique(String productKey, String deviceName) { + IotDeviceDO existingDevice = deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName); + if (existingDevice != null) { + throw exception(DEVICE_NAME_EXISTS); + } + } + + /** + * 生成唯一的 deviceKey + * + * @return 生成的 deviceKey + */ + private String generateUniqueDeviceKey() { + return UUID.randomUUID().toString(); + } + + /** + * 生成 deviceSecret + * + * @return 生成的 deviceSecret + */ + private String generateDeviceSecret() { + return IdUtil.fastSimpleUUID(); + } + + /** + * 生成 MQTT Client ID + * + * @return 生成的 MQTT Client ID + */ + private String generateMqttClientId() { + return UUID.randomUUID().toString(); + } + + /** + * 生成 MQTT Username + * + * @param deviceName 设备名称 + * @param productKey 产品 Key + * @return 生成的 MQTT Username + */ + private String generateMqttUsername(String deviceName, String productKey) { + return deviceName + "&" + productKey; + } + + /** + * 生成 MQTT Password + * + * @return 生成的 MQTT Password + */ + private String generateMqttPassword() { + // TODO @haohao:【后续优化】在实际应用中,建议使用更安全的方法生成 MQTT Password,如加密或哈希 + return UUID.randomUUID().toString(); + } + + /** + * 生成唯一的 DeviceName + * + * @param productKey 产品标识 + * @return 生成的唯一 DeviceName + */ + private String generateUniqueDeviceName(String productKey) { + for (int i = 0; i < Short.MAX_VALUE; i++) { + String deviceName = IdUtil.fastSimpleUUID().substring(0, 20); + if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) { + return deviceName; + } + } + throw new IllegalArgumentException("生成 DeviceName 失败"); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateDevice(IotDeviceSaveReqVO updateReqVO) { + // 1. 校验存在 + validateDeviceExists(updateReqVO.getId()); + + // 2. 更新到数据库 + IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class) + .setDeviceName(null).setProductId(null); // 设备名称 和 产品 ID 不能修改 + deviceMapper.updateById(updateObj); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteDevice(Long id) { + // 1.1 校验存在 + IotDeviceDO device = validateDeviceExists(id); + // 1.2 如果是网关设备,检查是否有子设备 + if (device.getGatewayId() != null && deviceMapper.selectCountByGatewayId(id) > 0) { + throw exception(DEVICE_HAS_CHILDREN); + } + + // 2. 删除设备 + deviceMapper.deleteById(id); + } + + /** + * 校验设备是否存在 + * + * @param id 设备 ID + * @return 设备对象 + */ + private IotDeviceDO validateDeviceExists(Long id) { + IotDeviceDO device = deviceMapper.selectById(id); + if (device == null) { + throw exception(DEVICE_NOT_EXISTS); + } + return device; + } + + @Override + public IotDeviceDO getDevice(Long id) { + IotDeviceDO device = deviceMapper.selectById(id); + if (device == null) { + throw exception(DEVICE_NOT_EXISTS); + } + return device; + } + + @Override + public PageResult getDevicePage(IotDevicePageReqVO pageReqVO) { + return deviceMapper.selectPage(pageReqVO); + } + + @Override + public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) { + // 校验存在 + validateDeviceExists(updateReqVO.getId()); + + // 更新状态和更新时间 + IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class); + deviceMapper.updateById(updateObj); + } + + @Override + public Long getDeviceCountByProductId(Long productId) { + return deviceMapper.selectCountByProductId(productId); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java new file mode 100644 index 000000000..032a7478e --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.iot.service.device; + +import jakarta.validation.*; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; + +/** + * IoT 设备 Service 接口 + * + * @author 芋道源码 + */ +public interface IotDeviceService { + + /** + * 创建设备 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createDevice(@Valid IotDeviceSaveReqVO createReqVO); + + /** + * 更新设备 + * + * @param updateReqVO 更新信息 + */ + void updateDevice(@Valid IotDeviceSaveReqVO updateReqVO); + + /** + * 删除设备 + * + * @param id 编号 + */ + void deleteDevice(Long id); + + /** + * 获得设备 + * + * @param id 编号 + * @return IoT 设备 + */ + IotDeviceDO getDevice(Long id); + + /** + * 获得设备分页 + * + * @param pageReqVO 分页查询 + * @return IoT 设备分页 + */ + PageResult getDevicePage(IotDevicePageReqVO pageReqVO); + + /** + * 更新设备状态 + * + * @param updateReqVO 更新信息 + */ + void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO); + + /** + * 获得设备数量 + * + * @param productId 产品编号 + * @return 设备数量 + */ + Long getDeviceCountByProductId(Long productId); +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java new file mode 100644 index 000000000..3fff94fd9 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java @@ -0,0 +1,71 @@ +package cn.iocoder.yudao.module.iot.service.product; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * IoT 产品 Service 接口 + * + * @author ahh + */ +public interface IotProductService { + + /** + * 创建产品 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProduct(@Valid IotProductSaveReqVO createReqVO); + + /** + * 更新产品 + * + * @param updateReqVO 更新信息 + */ + void updateProduct(@Valid IotProductSaveReqVO updateReqVO); + + /** + * 删除产品 + * + * @param id 编号 + */ + void deleteProduct(Long id); + + /** + * 获得产品 + * + * @param id 编号 + * @return 产品 + */ + IotProductDO getProduct(Long id); + + /** + * 获得产品分页 + * + * @param pageReqVO 分页查询 + * @return 产品分页 + */ + PageResult getProductPage(IotProductPageReqVO pageReqVO); + + /** + * 更新产品状态 + * + * @param id 编号 + * @param status 状态 + */ + void updateProductStatus(Long id, Integer status); + + /** + * 获得所有产品 + * + * @return 产品列表 + */ + List getProductList(); + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java new file mode 100644 index 000000000..15391f70b --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java @@ -0,0 +1,122 @@ +package cn.iocoder.yudao.module.iot.service.product; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; +import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper; +import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; + +/** + * IoT 产品 Service 实现类 + * + * @author ahh + */ +@Service +@Validated +public class IotProductServiceImpl implements IotProductService { + + @Resource + private IotProductMapper productMapper; + + @Override + public Long createProduct(IotProductSaveReqVO createReqVO) { + // 1. 生成 ProductKey + createProductKey(createReqVO); + // 2. 插入 + IotProductDO product = BeanUtils.toBean(createReqVO, IotProductDO.class); + productMapper.insert(product); + return product.getId(); + } + + /** + * 创建 ProductKey + * + * @param createReqVO 创建信息 + */ + private void createProductKey(IotProductSaveReqVO createReqVO) { + String productKey = createReqVO.getProductKey(); + // 1. productKey为空,生成随机的 11 位字符串 + if (StrUtil.isEmpty(productKey)) { + productKey = UUID.randomUUID().toString().replace("-", "").substring(0, 11); + } + // 2. 校验唯一性 + if (productMapper.selectByProductKey(productKey) != null) { + throw exception(PRODUCT_IDENTIFICATION_EXISTS); + } + createReqVO.setProductKey(productKey); + } + + @Override + public void updateProduct(IotProductSaveReqVO updateReqVO) { + updateReqVO.setProductKey(null); // 不更新产品标识 + // 1.1 校验存在 + IotProductDO iotProductDO = validateProductExists(updateReqVO.getId()); + // 1.2 发布状态不可更新 + validateProductStatus(iotProductDO); + // 2. 更新 + IotProductDO updateObj = BeanUtils.toBean(updateReqVO, IotProductDO.class); + productMapper.updateById(updateObj); + } + + @Override + public void deleteProduct(Long id) { + // 1.1 校验存在 + IotProductDO iotProductDO = validateProductExists(id); + // 1.2 发布状态不可删除 + validateProductStatus(iotProductDO); + // 2. 删除 + productMapper.deleteById(id); + } + + private IotProductDO validateProductExists(Long id) { + IotProductDO iotProductDO = productMapper.selectById(id); + if (iotProductDO == null) { + throw exception(PRODUCT_NOT_EXISTS); + } + return iotProductDO; + } + + private void validateProductStatus(IotProductDO iotProductDO) { + if (Objects.equals(iotProductDO.getStatus(), IotProductStatusEnum.PUBLISHED.getType())) { + throw exception(PRODUCT_STATUS_NOT_DELETE); + } + } + + @Override + public IotProductDO getProduct(Long id) { + return productMapper.selectById(id); + } + + @Override + public PageResult getProductPage(IotProductPageReqVO pageReqVO) { + return productMapper.selectPage(pageReqVO); + } + + @Override + public void updateProductStatus(Long id, Integer status) { + // 1. 校验存在 + validateProductExists(id); + // 2. 更新 + IotProductDO updateObj = IotProductDO.builder().id(id).status(status).build(); + productMapper.updateById(updateObj); + } + + @Override + public List getProductList() { + return productMapper.selectList(); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionService.java new file mode 100644 index 000000000..ce8e472f5 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionService.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.iot.service.thinkmodelfunction; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * IoT 产品物模型 Service 接口 + * + * @author 芋道源码 + */ +public interface IotThinkModelFunctionService { + + /** + * 创建产品物模型 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createThinkModelFunction(@Valid IotThinkModelFunctionSaveReqVO createReqVO); + + + /** + * 更新产品物模型 + * + * @param updateReqVO 更新信息 + */ + void updateThinkModelFunction(@Valid IotThinkModelFunctionSaveReqVO updateReqVO); + + /** + * 删除产品物模型 + * + * @param id 编号 + */ + void deleteThinkModelFunction(Long id); + + /** + * 获得产品物模型 + * + * @param id 编号 + * @return 产品物模型 + */ + IotThinkModelFunctionDO getThinkModelFunction(Long id); + + /** + * 获得产品物模型列表 + * + * @param productId 产品编号 + * @return 产品物模型列表 + */ + List getThinkModelFunctionListByProductId(Long productId); + + /** + * 获得产品物模型分页 + * + * @param pageReqVO 分页查询 + * @return 产品物模型分页 + */ + PageResult getThinkModelFunctionPage(IotThinkModelFunctionPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionServiceImpl.java new file mode 100644 index 000000000..793786944 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionServiceImpl.java @@ -0,0 +1,404 @@ +package cn.iocoder.yudao.module.iot.service.thinkmodelfunction; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelEvent; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelProperty; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelService; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType.ThingModelArgument; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType.ThingModelArraySpecs; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType.ThingModelArrayType; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType.ThingModelTextType; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionSaveReqVO; +import cn.iocoder.yudao.module.iot.convert.thinkmodelfunction.IotThinkModelFunctionConvert; +import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO; +import cn.iocoder.yudao.module.iot.dal.mysql.thinkmodelfunction.IotThinkModelFunctionMapper; +import cn.iocoder.yudao.module.iot.enums.product.IotAccessModeEnum; +import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.*; +import java.util.stream.Collectors; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; + +/** + * IoT 产品物模型 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class IotThinkModelFunctionServiceImpl implements IotThinkModelFunctionService { + + @Resource + private IotThinkModelFunctionMapper thinkModelFunctionMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createThinkModelFunction(IotThinkModelFunctionSaveReqVO createReqVO) { + // 1. 校验功能标识符在同一产品下是否唯一 + validateIdentifierUnique(createReqVO.getProductId(), createReqVO.getIdentifier()); + + // 2. 功能名称在同一产品下是否唯一 + validateNameUnique(createReqVO.getProductId(), createReqVO.getName()); + + // 3. 系统保留字段,不能用于标识符定义 + validateNotDefaultEventAndService(createReqVO.getIdentifier()); + + // 3. 插入数据库 + IotThinkModelFunctionDO function = IotThinkModelFunctionConvert.INSTANCE.convert(createReqVO); + thinkModelFunctionMapper.insert(function); + + // 4. 如果创建的是属性,需要更新默认的事件和服务 + if (Objects.equals(createReqVO.getType(), IotProductFunctionTypeEnum.PROPERTY.getType())) { + createDefaultEventsAndServices(createReqVO.getProductId(), createReqVO.getProductKey()); + } + return function.getId(); + } + + private void validateNotDefaultEventAndService(String identifier) { + // set, get, post, property, event, time, value 是系统保留字段,不能用于标识符定义 + if (CollUtil.containsAny(Arrays.asList("set", "get", "post", "property", "event", "time", "value"), Collections.singletonList(identifier))) { + throw exception(THINK_MODEL_FUNCTION_IDENTIFIER_INVALID); + } +// if (CollUtil.containsAny(Arrays.asList("post", "set", "get"), identifier)) { +// throw exception(THINK_MODEL_FUNCTION_IDENTIFIER_INVALID); +// } + } + + private void validateNameUnique(Long productId, String name) { + IotThinkModelFunctionDO function = thinkModelFunctionMapper.selectByProductIdAndName(productId, name); + if (function != null) { + throw exception(THINK_MODEL_FUNCTION_NAME_EXISTS); + } + } + + private void validateIdentifierUnique(Long productId, String identifier) { + IotThinkModelFunctionDO function = thinkModelFunctionMapper.selectByProductIdAndIdentifier(productId, identifier); + if (function != null) { + throw exception(THINK_MODEL_FUNCTION_IDENTIFIER_EXISTS); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateThinkModelFunction(IotThinkModelFunctionSaveReqVO updateReqVO) { + // 1. 校验功能是否存在 + validateThinkModelFunctionExists(updateReqVO.getId()); + + // 2. 校验功能标识符是否唯一 + validateIdentifierUniqueForUpdate(updateReqVO.getId(), updateReqVO.getProductId(), updateReqVO.getIdentifier()); + + // 3. 更新数据库 + IotThinkModelFunctionDO thinkModelFunction = IotThinkModelFunctionConvert.INSTANCE.convert(updateReqVO); + thinkModelFunctionMapper.updateById(thinkModelFunction); + + // 4. 如果更新的是属性,需要更新默认的事件和服务 + if (Objects.equals(updateReqVO.getType(), IotProductFunctionTypeEnum.PROPERTY.getType())) { + createDefaultEventsAndServices(updateReqVO.getProductId(), updateReqVO.getProductKey()); + } + } + + private void validateIdentifierUniqueForUpdate(Long id, Long productId, String identifier) { + IotThinkModelFunctionDO function = thinkModelFunctionMapper.selectByProductIdAndIdentifier(productId, identifier); + if (function != null && ObjectUtil.notEqual(function.getId(), id)) { + throw exception(THINK_MODEL_FUNCTION_IDENTIFIER_EXISTS); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteThinkModelFunction(Long id) { + // 1. 校验功能是否存在 + IotThinkModelFunctionDO functionDO = thinkModelFunctionMapper.selectById(id); + if (functionDO == null) { + throw exception(THINK_MODEL_FUNCTION_NOT_EXISTS); + } + + // 2. 删除功能 + thinkModelFunctionMapper.deleteById(id); + + // 3. 如果删除的是属性,需要更新默认的事件和服务 + if (Objects.equals(functionDO.getType(), IotProductFunctionTypeEnum.PROPERTY.getType())) { + createDefaultEventsAndServices(functionDO.getProductId(), functionDO.getProductKey()); + } + } + + /** + * 校验功能是否存在 + * + * @param id 功能编号 + */ + private void validateThinkModelFunctionExists(Long id) { + if (thinkModelFunctionMapper.selectById(id) == null) { + throw exception(THINK_MODEL_FUNCTION_NOT_EXISTS); + } + } + + @Override + public IotThinkModelFunctionDO getThinkModelFunction(Long id) { + return thinkModelFunctionMapper.selectById(id); + } + + @Override + public List getThinkModelFunctionListByProductId(Long productId) { + return thinkModelFunctionMapper.selectListByProductId(productId); + } + + @Override + public PageResult getThinkModelFunctionPage(IotThinkModelFunctionPageReqVO pageReqVO) { + return thinkModelFunctionMapper.selectPage(pageReqVO); + } + + /** + * 创建默认的事件和服务 + */ + public void createDefaultEventsAndServices(Long productId, String productKey) { + // 1. 获取当前属性列表 + List propertyList = thinkModelFunctionMapper + .selectListByProductIdAndType(productId, IotProductFunctionTypeEnum.PROPERTY.getType()); + + // 2. 生成新的事件和服务列表 + List newFunctionList = new ArrayList<>(); + // 生成属性上报事件 + ThingModelEvent propertyPostEvent = generatePropertyPostEvent(propertyList); + if (propertyPostEvent != null) { + IotThinkModelFunctionDO eventFunction = buildEventFunctionDO(productId, productKey, propertyPostEvent); + newFunctionList.add(eventFunction); + } + // 生成属性设置服务 + ThingModelService propertySetService = generatePropertySetService(propertyList); + if (propertySetService != null) { + IotThinkModelFunctionDO setServiceFunction = buildServiceFunctionDO(productId, productKey, propertySetService); + newFunctionList.add(setServiceFunction); + } + // 生成属性获取服务 + ThingModelService propertyGetService = generatePropertyGetService(propertyList); + if (propertyGetService != null) { + IotThinkModelFunctionDO getServiceFunction = buildServiceFunctionDO(productId, productKey, propertyGetService); + newFunctionList.add(getServiceFunction); + } + + // 3. 获取数据库中的默认的旧事件和服务列表 + List oldFunctionList = thinkModelFunctionMapper.selectListByProductIdAndIdentifiersAndTypes( + productId, + Arrays.asList("post", "set", "get"), + Arrays.asList(IotProductFunctionTypeEnum.EVENT.getType(), IotProductFunctionTypeEnum.SERVICE.getType()) + ); + + // 3.1 使用 diffList 方法比较新旧列表 + List> diffResult = diffList(oldFunctionList, newFunctionList, + // 继续使用 identifier 和 type 进行比较:这样可以准确地匹配对应的功能对象。 + (oldFunc, newFunc) -> Objects.equals(oldFunc.getIdentifier(), newFunc.getIdentifier()) + && Objects.equals(oldFunc.getType(), newFunc.getType())); + List createList = diffResult.get(0); // 需要新增的 + List updateList = diffResult.get(1); // 需要更新的 + List deleteList = diffResult.get(2); // 需要删除的 + + // 3.2 批量执行数据库操作 + // 新增数据库中的新事件和服务列表 + if (CollUtil.isNotEmpty(createList)) { + thinkModelFunctionMapper.insertBatch(createList); + } + // 更新数据库中的事件和服务列表 + if (CollUtil.isNotEmpty(updateList)) { + // 首先,为每个需要更新的对象设置其对应的 ID + updateList.forEach(updateFunc -> { + IotThinkModelFunctionDO oldFunc = findFunctionByIdentifierAndType( + oldFunctionList, updateFunc.getIdentifier(), updateFunc.getType()); + if (oldFunc != null) { + updateFunc.setId(oldFunc.getId()); + } + }); + // 过滤掉没有设置 ID 的对象 + List validUpdateList = updateList.stream() + .filter(func -> func.getId() != null) + .collect(Collectors.toList()); + // 执行批量更新 + if (CollUtil.isNotEmpty(validUpdateList)) { + thinkModelFunctionMapper.updateBatch(validUpdateList); + } + } + + // 删除数据库中的旧事件和服务列表 + if (CollUtil.isNotEmpty(deleteList)) { + Set idsToDelete = CollectionUtils.convertSet(deleteList, IotThinkModelFunctionDO::getId); + thinkModelFunctionMapper.deleteByIds(idsToDelete); + } + } + + /** + * 根据标识符和类型查找功能对象 + */ + private IotThinkModelFunctionDO findFunctionByIdentifierAndType(List functionList, + String identifier, Integer type) { + return CollUtil.findOne(functionList, func -> + Objects.equals(func.getIdentifier(), identifier) && Objects.equals(func.getType(), type)); + } + + /** + * 构建事件功能对象 + */ + private IotThinkModelFunctionDO buildEventFunctionDO(Long productId, String productKey, ThingModelEvent event) { + return new IotThinkModelFunctionDO() + .setProductId(productId) + .setProductKey(productKey) + .setIdentifier(event.getIdentifier()) + .setName(event.getName()) + .setDescription(event.getDescription()) + .setType(IotProductFunctionTypeEnum.EVENT.getType()) + .setEvent(event); + } + + /** + * 构建服务功能对象 + */ + private IotThinkModelFunctionDO buildServiceFunctionDO(Long productId, String productKey, ThingModelService service) { + return new IotThinkModelFunctionDO() + .setProductId(productId) + .setProductKey(productKey) + .setIdentifier(service.getIdentifier()) + .setName(service.getName()) + .setDescription(service.getDescription()) + .setType(IotProductFunctionTypeEnum.SERVICE.getType()) + .setService(service); + } + + /** + * 生成属性上报事件 + */ + private ThingModelEvent generatePropertyPostEvent(List propertyList) { + if (CollUtil.isEmpty(propertyList)) { + return null; + } + + ThingModelEvent event = new ThingModelEvent() + .setIdentifier("post") + .setName("属性上报") + .setType("info") + .setDescription("属性上报事件") + .setMethod("thing.event.property.post"); + + // 将属性列表转换为事件的输出参数 + List outputData = new ArrayList<>(); + for (IotThinkModelFunctionDO functionDO : propertyList) { + ThingModelProperty property = functionDO.getProperty(); + ThingModelArgument arg = new ThingModelArgument() + .setIdentifier(property.getIdentifier()) + .setName(property.getName()) + .setDataType(property.getDataType()) + .setDescription(property.getDescription()) + .setDirection("output"); // 设置为输出参数 + outputData.add(arg); + } + event.setOutputData(outputData); + return event; + } + + /** + * 生成属性设置服务 + */ + private ThingModelService generatePropertySetService(List propertyList) { + if (propertyList == null || propertyList.isEmpty()) { + return null; + } + + List inputData = new ArrayList<>(); + for (IotThinkModelFunctionDO functionDO : propertyList) { + ThingModelProperty property = functionDO.getProperty(); + if (IotAccessModeEnum.WRITE.getMode().equals(property.getAccessMode()) || IotAccessModeEnum.READ_WRITE.getMode().equals(property.getAccessMode())) { + ThingModelArgument arg = new ThingModelArgument() + .setIdentifier(property.getIdentifier()) + .setName(property.getName()) + .setDataType(property.getDataType()) + .setDescription(property.getDescription()) + .setDirection("input"); // 设置为输入参数 + inputData.add(arg); + } + } + if (inputData.isEmpty()) { + // 如果没有可写属性,不生成属性设置服务 + return null; + } + + // 属性设置服务一般不需要输出参数 + return new ThingModelService() + .setIdentifier("set") + .setName("属性设置") + .setCallType("async") + .setDescription("属性设置服务") + .setMethod("thing.service.property.set") + .setInputData(inputData) + // 属性设置服务一般不需要输出参数 + .setOutputData(new ArrayList<>()); + } + + /** + * 生成属性获取服务 + */ + private ThingModelService generatePropertyGetService(List propertyList) { + if (propertyList == null || propertyList.isEmpty()) { + return null; + } + + List outputData = new ArrayList<>(); + for (IotThinkModelFunctionDO functionDO : propertyList) { + ThingModelProperty property = functionDO.getProperty(); + if (ObjectUtils.equalsAny(property.getAccessMode(), + IotAccessModeEnum.READ.getMode(), IotAccessModeEnum.READ_WRITE.getMode())) { + ThingModelArgument arg = new ThingModelArgument() + .setIdentifier(property.getIdentifier()) + .setName(property.getName()) + .setDataType(property.getDataType()) + .setDescription(property.getDescription()) + .setDirection("output"); // 设置为输出参数 + outputData.add(arg); + } + } + if (outputData.isEmpty()) { + // 如果没有可读属性,不生成属性获取服务 + return null; + } + + ThingModelService service = new ThingModelService() + .setIdentifier("get") + .setName("属性获取") + .setCallType("async") + .setDescription("属性获取服务") + .setMethod("thing.service.property.get"); + + // 定义输入参数:属性标识符列表 + ThingModelArgument inputArg = new ThingModelArgument() + .setIdentifier("properties") + .setName("属性标识符列表") + .setDescription("需要获取的属性标识符列表") + .setDirection("input"); // 设置为输入参数 + + // 创建数组类型,元素类型为文本类型(字符串) + ThingModelArrayType arrayType = new ThingModelArrayType(); + arrayType.setType("array"); + ThingModelArraySpecs arraySpecs = new ThingModelArraySpecs(); + ThingModelTextType textType = new ThingModelTextType(); + textType.setType("text"); + arraySpecs.setItem(textType); + arrayType.setSpecs(arraySpecs); + inputArg.setDataType(arrayType); + + service.setInputData(Collections.singletonList(inputArg)); + service.setOutputData(outputData); + return service; + } + +} diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index d0c429aab..efd53c84a 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -33,11 +33,11 @@ - - cn.iocoder.boot - yudao-module-member-biz - ${revision} - + + + + + @@ -52,11 +52,11 @@ - - cn.iocoder.boot - yudao-module-pay-biz - ${revision} - + + + + + @@ -66,26 +66,26 @@ - - cn.iocoder.boot - yudao-module-promotion-biz - ${revision} - - - cn.iocoder.boot - yudao-module-product-biz - ${revision} - - - cn.iocoder.boot - yudao-module-trade-biz - ${revision} - - - cn.iocoder.boot - yudao-module-statistics-biz - ${revision} - + + + + + + + + + + + + + + + + + + + + @@ -106,6 +106,13 @@ + + + + + + + diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java index 3f4bae04f..2bf6e5277 100644 --- a/yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/controller/DefaultController.java @@ -65,4 +65,10 @@ public class DefaultController { "[AI 大模型 yudao-module-ai - 已禁用][参考 https://doc.iocoder.cn/ai/build/ 开启]"); } + @RequestMapping(value = {"/admin-api/iot/**"}) + public CommonResult iot404() { + return CommonResult.error(NOT_IMPLEMENTED.getCode(), + "[IOT 物联网 yudao-module-iot - 已禁用][参考 https://doc.iocoder.cn/iot/build/ 开启]"); + } + } diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index 5a4fa9286..5457247e6 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/yudao-server/src/main/resources/application-dev.yaml @@ -193,4 +193,23 @@ justauth: cache: type: REDIS prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE:: - timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟 \ No newline at end of file + timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟 + + +--- #################### iot相关配置 TODO 芋艿:再瞅瞅 #################### +iot: + emq: + # 账号 + username: anhaohao + # 密码 + password: ahh@123456 + # 主机地址 + hostUrl: tcp://chaojiniu.top:1883 + # 客户端Id,不能相同,采用随机数 ${random.value} + client-id: ${random.int} + # 默认主题 + default-topic: test + # 保持连接 + keepalive: 60 + # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) + clearSession: true \ No newline at end of file diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 40c0919b7..e5ae6d195 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -45,8 +45,8 @@ spring: primary: master datasource: master: - url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 - # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例 + url: jdbc:mysql://127.0.0.1:3307/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 + # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai # MySQL Connector/J 5.X 连接的示例 # url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer 连接的示例 @@ -54,7 +54,7 @@ spring: # url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例 # url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例 username: root - password: 123456 + password: ahh@123456 # username: sa # SQL Server 连接的示例 # password: Yudao@2024 # SQL Server 连接的示例 # username: SYSDBA # DM 连接的示例 @@ -63,9 +63,9 @@ spring: # password: Yudao@2024 # OpenGauss 连接的示例 slave: # 模拟从库,可根据自己需要修改 lazy: true # 开启懒加载,保证启动速度 - url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true + url: jdbc:mysql://127.0.0.1:3307/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: root - password: 123456 + password: ahh@123456 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 data: @@ -174,6 +174,7 @@ logging: cn.iocoder.yudao.module.statistics.dal.mysql: debug cn.iocoder.yudao.module.crm.dal.mysql: debug cn.iocoder.yudao.module.erp.dal.mysql: debug + cn.iocoder.yudao.module.iot.dal.mysql: debug cn.iocoder.yudao.module.ai.dal.mysql: debug org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 @@ -254,3 +255,20 @@ justauth: prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE:: timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟 +--- #################### iot相关配置 TODO 芋艿:再瞅瞅 #################### +iot: + emq: + # 账号 + username: anhaohao + # 密码 + password: ahh@123456 + # 主机地址 + hostUrl: tcp://chaojiniu.top:1883 + # 客户端Id,不能相同,采用随机数 ${random.value} + client-id: ${random.int} + # 默认主题 + default-topic: test + # 保持连接 + keepalive: 60 + # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) + clearSession: true \ No newline at end of file