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