services;
+
+}
\ 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/device/IotDeviceMessageDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceMessageDO.java
new file mode 100644
index 0000000000..9f1f6a6a0c
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceMessageDO.java
@@ -0,0 +1,109 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.device;
+
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
+import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * IoT 设备消息数据 DO
+ *
+ * 目前使用 TDengine 存储
+ *
+ * @author alwayssuper
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDeviceMessageDO {
+
+ /**
+ * 消息编号
+ */
+ private String id;
+ /**
+ * 上报时间戳
+ */
+ private Long reportTime;
+ /**
+ * 存储时间戳
+ */
+ private Long ts;
+
+ /**
+ * 设备编号
+ *
+ * 关联 {@link IotDeviceDO#getId()}
+ */
+ private Long deviceId;
+ /**
+ * 租户编号
+ */
+ private Long tenantId;
+
+ /**
+ * 服务编号,该消息由哪个 server 发送
+ */
+ private String serverId;
+
+ /**
+ * 是否上行消息
+ *
+ * 由 {@link cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils#isUpstreamMessage(IotDeviceMessage)} 计算。
+ * 计算并存储的目的:方便计算多少条上行、多少条下行
+ */
+ private Boolean upstream;
+ /**
+ * 是否回复消息
+ *
+ * 由 {@link cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils#isReplyMessage(IotDeviceMessage)} 计算。
+ * 计算并存储的目的:方便计算多少条请求、多少条回复
+ */
+ private Boolean reply;
+ /**
+ * 标识符
+ *
+ * 例如说:{@link IotThingModelDO#getIdentifier()}
+ * 目前,只有事件上报、服务调用才有!!!
+ */
+ private String identifier;
+
+ // ========== codec(编解码)字段 ==========
+
+ /**
+ * 请求编号
+ *
+ * 由设备生成,对应阿里云 IoT 的 Alink 协议中的 id、华为云 IoTDA 协议的 request_id
+ */
+ private String requestId;
+ /**
+ * 请求方法
+ *
+ * 枚举 {@link IotDeviceMessageMethodEnum}
+ * 例如说:thing.property.report 属性上报
+ */
+ private String method;
+ /**
+ * 请求参数
+ *
+ * 例如说:属性上报的 properties、事件上报的 params
+ */
+ private Object params;
+ /**
+ * 响应结果
+ */
+ private Object data;
+ /**
+ * 响应错误码
+ */
+ private Integer code;
+ /**
+ * 响应提示
+ */
+ private String msg;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskDO.java
new file mode 100644
index 0000000000..4c9124b89f
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskDO.java
@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.ota;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskDeviceScopeEnum;
+import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * IoT OTA 升级任务 DO
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "iot_ota_task", autoResultMap = true)
+@KeySequence("iot_ota_task_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotOtaTaskDO extends BaseDO {
+
+ /**
+ * 任务编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 任务名称
+ */
+ private String name;
+ /**
+ * 任务描述
+ */
+ private String description;
+
+ /**
+ * 固件编号
+ *
+ * 关联 {@link IotOtaFirmwareDO#getId()}
+ */
+ private Long firmwareId;
+
+ /**
+ * 任务状态
+ *
+ * 关联 {@link IotOtaTaskStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * 设备升级范围
+ *
+ * 关联 {@link IotOtaTaskDeviceScopeEnum}
+ */
+ private Integer deviceScope;
+ /**
+ * 设备总数数量
+ */
+ private Integer deviceTotalCount;
+ /**
+ * 设备成功数量
+ */
+ private Integer deviceSuccessCount;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskRecordDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskRecordDO.java
new file mode 100644
index 0000000000..d99a1bb60a
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskRecordDO.java
@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.ota;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
+import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * IoT OTA 升级任务记录 DO
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "iot_ota_task_record", autoResultMap = true)
+@KeySequence("iot_ota_task_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotOtaTaskRecordDO extends BaseDO {
+
+ public static final String DESCRIPTION_CANCEL_BY_TASK = "管理员手动取消升级任务(批量)";
+
+ public static final String DESCRIPTION_CANCEL_BY_RECORD = "管理员手动取消升级记录(单个)";
+
+ /**
+ * 升级记录编号
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 固件编号
+ *
+ * 关联 {@link IotOtaFirmwareDO#getId()}
+ */
+ private Long firmwareId;
+ /**
+ * 任务编号
+ *
+ * 关联 {@link IotOtaTaskDO#getId()}
+ */
+ private Long taskId;
+
+ /**
+ * 设备编号
+ *
+ * 关联 {@link IotDeviceDO#getId()}
+ */
+ private Long deviceId;
+ /**
+ * 来源的固件编号
+ *
+ * 关联 {@link IotDeviceDO#getFirmwareId()}
+ */
+ private Long fromFirmwareId;
+
+ /**
+ * 升级状态
+ *
+ * 关联 {@link IotOtaTaskRecordStatusEnum}
+ */
+ private Integer status;
+ /**
+ * 升级进度,百分比
+ */
+ private Integer progress;
+ /**
+ * 升级进度描述
+ *
+ * 注意,只记录设备最后一次的升级进度描述
+ * 如果想看历史记录,可以查看 {@link IotDeviceMessageDO} 设备日志
+ */
+ 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/dal/dataobject/rule/IotDataRuleDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotDataRuleDO.java
new file mode 100644
index 0000000000..191df10d06
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotDataRuleDO.java
@@ -0,0 +1,109 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
+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.dataobject.thingmodel.IotThingModelDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * IoT 数据流转规则 DO
+ *
+ * 监听 {@link SourceConfig} 数据源,转发到 {@link IotDataSinkDO} 数据目的
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "iot_data_rule", autoResultMap = true)
+@KeySequence("iot_data_rule_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDataRuleDO extends BaseDO {
+
+ /**
+ * 数据流转规格编号
+ */
+ private Long id;
+ /**
+ * 数据流转规格名称
+ */
+ private String name;
+ /**
+ * 数据流转规格描述
+ */
+ private String description;
+ /**
+ * 数据流转规格状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * 数据源配置数组
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private List sourceConfigs;
+ /**
+ * 数据目的编号数组
+ *
+ * 关联 {@link IotDataSinkDO#getId()}
+ */
+ @TableField(typeHandler = LongListTypeHandler.class)
+ private List sinkIds;
+
+ // TODO @芋艿:未来考虑使用 groovy;支持数据处理;
+
+ /**
+ * 数据源配置
+ */
+ @Data
+ public static class SourceConfig {
+
+ /**
+ * 消息方法
+ *
+ * 枚举 {@link IotDeviceMessageMethodEnum} 中的 upstream 上行部分
+ */
+ @NotEmpty(message = "消息方法不能为空")
+ private String method;
+
+ /**
+ * 产品编号
+ *
+ * 关联 {@link IotProductDO#getId()}
+ */
+ private Long productId;
+ /**
+ * 设备编号
+ *
+ * 关联 {@link IotDeviceDO#getId()}
+ * 特殊:如果为 {@link IotDeviceDO#DEVICE_ID_ALL} 时,则是全部设备
+ */
+ @NotEmpty(message = "设备编号不能为空")
+ private Long deviceId;
+
+ /**
+ * 标识符
+ *
+ * 1. 物模型时,对应:{@link IotThingModelDO#getIdentifier()}
+ */
+ private String identifier;
+
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotSceneRuleDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotSceneRuleDO.java
new file mode 100644
index 0000000000..94aa1eb5a3
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotSceneRuleDO.java
@@ -0,0 +1,245 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
+import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
+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.dataobject.thingmodel.IotThingModelDO;
+import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
+import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
+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;
+
+import java.util.List;
+
+/**
+ * IoT 场景联动规则 DO
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "iot_scene_rule", autoResultMap = true)
+@KeySequence("iot_scene_rule_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotSceneRuleDO extends TenantBaseDO {
+
+ /**
+ * 场景联动编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 场景联动名称
+ */
+ private String name;
+ /**
+ * 场景联动描述
+ */
+ private String description;
+ /**
+ * 场景联动状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+
+ /**
+ * 场景定义配置
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private List triggers;
+
+ /**
+ * 场景动作配置
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private List actions;
+
+ /**
+ * 场景定义配置
+ */
+ @Data
+ public static class Trigger {
+
+ // ========== 事件部分 ==========
+
+ /**
+ * 场景事件类型
+ *
+ * 枚举 {@link IotSceneRuleTriggerTypeEnum}
+ * 1. {@link IotSceneRuleTriggerTypeEnum#DEVICE_STATE_UPDATE} 时,operator 非空,并且 value 为在线状态
+ * 2. {@link IotSceneRuleTriggerTypeEnum#DEVICE_PROPERTY_POST}
+ * {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST} 时,identifier、operator 非空,并且 value 为属性值
+ * 3. {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST}
+ * {@link IotSceneRuleTriggerTypeEnum#DEVICE_SERVICE_INVOKE} 时,identifier 非空,但是 operator、value 为空
+ * 4. {@link IotSceneRuleTriggerTypeEnum#TIMER} 时,conditions 非空,并且设备无关(无需 productId、deviceId 字段)
+ */
+ private Integer type;
+
+ /**
+ * 产品编号
+ *
+ * 关联 {@link IotProductDO#getId()}
+ */
+ private Long productId;
+ /**
+ * 设备编号
+ *
+ * 关联 {@link IotDeviceDO#getId()}
+ * 特殊:如果为 {@link IotDeviceDO#DEVICE_ID_ALL} 时,则是全部设备
+ */
+ private Long deviceId;
+ /**
+ * 物模型标识符
+ *
+ * 对应:{@link IotThingModelDO#getIdentifier()}
+ */
+ private String identifier;
+ /**
+ * 操作符
+ *
+ * 枚举 {@link IotSceneRuleConditionOperatorEnum}
+ */
+ private String operator;
+ /**
+ * 参数(属性值、在线状态)
+ *
+ * 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。
+ * 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN}
+ */
+ private String value;
+
+ /**
+ * CRON 表达式
+ */
+ private String cronExpression;
+
+ // ========== 条件部分 ==========
+
+ /**
+ * 触发条件分组(状态条件分组)的数组
+ *
+ * 第一层 List:分组与分组之间,是“或”的关系
+ * 第二层 List:条件与条件之间,是“且”的关系
+ */
+ private List> conditionGroups;
+
+ }
+
+ /**
+ * 触发条件(状态条件)
+ */
+ @Data
+ public static class TriggerCondition {
+
+ /**
+ * 触发条件类型
+ *
+ * 枚举 {@link IotSceneRuleConditionTypeEnum}
+ * 1. {@link IotSceneRuleConditionTypeEnum#DEVICE_STATE} 时,operator 非空,并且 value 为在线状态
+ * 2. {@link IotSceneRuleConditionTypeEnum#DEVICE_PROPERTY} 时,identifier、operator 非空,并且 value 为属性值
+ * 3. {@link IotSceneRuleConditionTypeEnum#CURRENT_TIME} 时,operator 非空(使用 DATE_TIME_ 和 TIME_ 部分),并且 value 非空
+ */
+ private Integer type;
+
+ /**
+ * 产品编号
+ *
+ * 关联 {@link IotProductDO#getId()}
+ */
+ private Long productId;
+ /**
+ * 设备编号
+ *
+ * 关联 {@link IotDeviceDO#getId()}
+ */
+ private Long deviceId;
+ /**
+ * 标识符(属性)
+ *
+ * 关联 {@link IotThingModelDO#getIdentifier()}
+ */
+ private String identifier;
+ /**
+ * 操作符
+ *
+ * 枚举 {@link IotSceneRuleConditionOperatorEnum}
+ */
+ private String operator;
+ /**
+ * 参数
+ *
+ * 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。
+ * 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN}
+ */
+ private String param;
+
+ }
+
+ /**
+ * 场景动作配置
+ */
+ @Data
+ public static class Action {
+
+ /**
+ * 执行类型
+ *
+ * 枚举 {@link IotSceneRuleActionTypeEnum}
+ * 1. {@link IotSceneRuleActionTypeEnum#DEVICE_PROPERTY_SET} 时,params 非空
+ * {@link IotSceneRuleActionTypeEnum#DEVICE_SERVICE_INVOKE} 时,params 非空
+ * 2. {@link IotSceneRuleActionTypeEnum#ALERT_TRIGGER} 时,alertConfigId 为空,因为是 {@link IotAlertConfigDO} 里面关联它
+ * 3. {@link IotSceneRuleActionTypeEnum#ALERT_RECOVER} 时,alertConfigId 非空
+ */
+ private Integer type;
+
+ /**
+ * 产品编号
+ *
+ * 关联 {@link IotProductDO#getId()}
+ */
+ private Long productId;
+ /**
+ * 设备编号
+ *
+ * 关联 {@link IotDeviceDO#getId()}
+ */
+ private Long deviceId;
+
+ /**
+ * 标识符(服务)
+ *
+ * 关联 {@link IotThingModelDO#getIdentifier()}
+ */
+ private String identifier;
+
+ /**
+ * 请求参数
+ *
+ * 一般来说,对应 {@link IotDeviceMessage#getParams()} 请求参数
+ */
+ private String params;
+
+ /**
+ * 告警配置编号
+ *
+ * 关联 {@link IotAlertConfigDO#getId()}
+ */
+ private Long alertConfigId;
+
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotAbstractDataSinkConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotAbstractDataSinkConfig.java
new file mode 100644
index 0000000000..68a8fd699b
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotAbstractDataSinkConfig.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import lombok.Data;
+
+/**
+ * IoT IotDataBridgeConfig 抽象类
+ *
+ * 用于表示数据目的配置数据的通用类型,根据具体的 "type" 字段动态映射到对应的子类
+ * 提供多态支持,适用于不同类型的数据结构序列化和反序列化场景。
+ *
+ * @author HUIHUI
+ */
+@Data
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = IotDataSinkHttpConfig.class, name = "1"),
+ @JsonSubTypes.Type(value = IotDataSinkMqttConfig.class, name = "10"),
+ @JsonSubTypes.Type(value = IotDataSinkRedisConfig.class, name = "21"),
+ @JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"),
+ @JsonSubTypes.Type(value = IotDataSinkRabbitMQConfig.class, name = "31"),
+ @JsonSubTypes.Type(value = IotDataSinkKafkaConfig.class, name = "32"),
+})
+public abstract class IotAbstractDataSinkConfig {
+
+ /**
+ * 配置类型
+ *
+ * 枚举 {@link IotDataSinkTypeEnum#getType()}
+ */
+ private String type;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java
new file mode 100644
index 0000000000..07460ac368
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.enums.rule.IotRedisDataStructureEnum;
+import lombok.Data;
+
+/**
+ * IoT Redis 配置 {@link IotAbstractDataSinkConfig} 实现类
+ *
+ * @author HUIHUI
+ */
+@Data
+public class IotDataSinkRedisConfig extends IotAbstractDataSinkConfig {
+
+ /**
+ * Redis 服务器地址
+ */
+ private String host;
+ /**
+ * 端口
+ */
+ private Integer port;
+ /**
+ * 密码
+ */
+ private String password;
+ /**
+ * 数据库索引
+ */
+ private Integer database;
+
+ /**
+ * Redis 数据结构类型
+ *
+ * 枚举 {@link IotRedisDataStructureEnum}
+ */
+ @InEnum(IotRedisDataStructureEnum.class)
+ private Integer dataStructure;
+
+ /**
+ * 主题/键名
+ *
+ * 对于不同的数据结构:
+ * - Stream: 流的键名
+ * - Hash: Hash 的键名
+ * - List: 列表的键名
+ * - Set: 集合的键名
+ * - ZSet: 有序集合的键名
+ * - String: 字符串的键名
+ */
+ private String topic;
+
+ /**
+ * Hash 字段名(仅当 dataStructure 为 HASH 时使用)
+ */
+ private String hashField;
+
+ /**
+ * ZSet 分数字段(仅当 dataStructure 为 ZSET 时使用)
+ * 指定消息中哪个字段作为分数,如果不指定则使用当前时间戳
+ */
+ private String scoreField;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelBoolOrEnumDataSpecs.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelBoolOrEnumDataSpecs.java
new file mode 100644
index 0000000000..8533fcc6f5
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelBoolOrEnumDataSpecs.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * IoT 物模型数据类型为布尔型或枚举型的 DataSpec 定义
+ *
+ * 数据类型,取值为 bool 或 enum
+ *
+ * @author HUIHUI
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@JsonIgnoreProperties({"dataType"}) // 忽略子类中的 dataType 字段,从而避免重复
+public class ThingModelBoolOrEnumDataSpecs extends ThingModelDataSpecs {
+
+ @NotEmpty(message = "枚举项的名称不能为空")
+ @Pattern(regexp = "^[\\u4e00-\\u9fa5a-zA-Z0-9][\\u4e00-\\u9fa5a-zA-Z0-9_-]{0,19}$",
+ message = "枚举项的名称只能包含中文、大小写英文字母、数字、下划线和短划线,必须以中文、英文字母或数字开头,长度不超过 20 个字符")
+ private String name;
+
+ @NotNull(message = "枚举值不能为空")
+ private Integer value;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java
new file mode 100644
index 0000000000..c5d7154ff6
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.alert;
+
+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.framework.mybatis.core.util.MyBatisUtils;
+import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * IoT 告警配置 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface IotAlertConfigMapper extends BaseMapperX {
+
+ default PageResult selectPage(IotAlertConfigPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(IotAlertConfigDO::getName, reqVO.getName())
+ .eqIfPresent(IotAlertConfigDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(IotAlertConfigDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(IotAlertConfigDO::getId));
+ }
+
+ default List selectListByStatus(Integer status) {
+ return selectList(IotAlertConfigDO::getStatus, status);
+ }
+
+ default List selectListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(IotAlertConfigDO::getStatus, status)
+ .apply(MyBatisUtils.findInSet("scene_rule_id", sceneRuleId)));
+ }
+
+}
\ 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/alert/IotAlertRecordMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertRecordMapper.java
new file mode 100644
index 0000000000..f23fe60f74
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertRecordMapper.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.alert;
+
+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.alert.vo.recrod.IotAlertRecordPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * IoT 告警记录 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface IotAlertRecordMapper extends BaseMapperX {
+
+ default PageResult selectPage(IotAlertRecordPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(IotAlertRecordDO::getConfigId, reqVO.getConfigId())
+ .eqIfPresent(IotAlertRecordDO::getConfigLevel, reqVO.getLevel())
+ .eqIfPresent(IotAlertRecordDO::getProductId, reqVO.getProductId())
+ .eqIfPresent(IotAlertRecordDO::getDeviceId, reqVO.getDeviceId())
+ .eqIfPresent(IotAlertRecordDO::getProcessStatus, reqVO.getProcessStatus())
+ .betweenIfPresent(IotAlertRecordDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(IotAlertRecordDO::getId));
+ }
+
+ default List selectListBySceneRuleId(Long sceneRuleId, Long deviceId, Boolean processStatus) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(IotAlertRecordDO::getSceneRuleId, sceneRuleId)
+ .eqIfPresent(IotAlertRecordDO::getDeviceId, deviceId)
+ .eqIfPresent(IotAlertRecordDO::getProcessStatus, processStatus)
+ .orderByDesc(IotAlertRecordDO::getId));
+ }
+
+ default int updateList(Collection ids, IotAlertRecordDO updateObj) {
+ return update(updateObj, new LambdaUpdateWrapper()
+ .in(IotAlertRecordDO::getId, ids));
+ }
+
+}
\ 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/ota/IotOtaTaskMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskMapper.java
new file mode 100644
index 0000000000..cf73231234
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskMapper.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.ota;
+
+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.ota.vo.task.IotOtaTaskPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface IotOtaTaskMapper extends BaseMapperX {
+
+ default IotOtaTaskDO selectByFirmwareIdAndName(Long firmwareId, String name) {
+ return selectOne(IotOtaTaskDO::getFirmwareId, firmwareId,
+ IotOtaTaskDO::getName, name);
+ }
+
+ default PageResult selectPage(IotOtaTaskPageReqVO pageReqVO) {
+ return selectPage(pageReqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(IotOtaTaskDO::getFirmwareId, pageReqVO.getFirmwareId())
+ .likeIfPresent(IotOtaTaskDO::getName, pageReqVO.getName())
+ .orderByDesc(IotOtaTaskDO::getId));
+ }
+
+ default int updateByIdAndStatus(Long id, Integer whereStatus, IotOtaTaskDO updateObj) {
+ return update(updateObj, new LambdaUpdateWrapper()
+ .eq(IotOtaTaskDO::getId, id)
+ .eq(IotOtaTaskDO::getStatus, whereStatus));
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskRecordMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskRecordMapper.java
new file mode 100644
index 0000000000..017adc9192
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskRecordMapper.java
@@ -0,0 +1,80 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.ota;
+
+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.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+@Mapper
+public interface IotOtaTaskRecordMapper extends BaseMapperX {
+
+ default List selectListByFirmwareIdAndTaskId(Long firmwareId, Long taskId) {
+ return selectList(new LambdaQueryWrapperX()
+ .eqIfPresent(IotOtaTaskRecordDO::getFirmwareId, firmwareId)
+ .eqIfPresent(IotOtaTaskRecordDO::getTaskId, taskId)
+ .select(IotOtaTaskRecordDO::getDeviceId, IotOtaTaskRecordDO::getStatus));
+ }
+
+ default PageResult selectPage(IotOtaTaskRecordPageReqVO pageReqVO) {
+ return selectPage(pageReqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(IotOtaTaskRecordDO::getTaskId, pageReqVO.getTaskId())
+ .eqIfPresent(IotOtaTaskRecordDO::getStatus, pageReqVO.getStatus()));
+ }
+
+ default List selectListByTaskIdAndStatus(Long taskId, Collection statuses) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(IotOtaTaskRecordDO::getTaskId, taskId)
+ .in(IotOtaTaskRecordDO::getStatus, statuses));
+ }
+
+ default Long selectCountByTaskIdAndStatus(Long taskId, Collection statuses) {
+ return selectCount(new LambdaQueryWrapperX()
+ .eq(IotOtaTaskRecordDO::getTaskId, taskId)
+ .in(IotOtaTaskRecordDO::getStatus, statuses));
+ }
+
+ default int updateByIdAndStatus(Long id, Integer status,
+ IotOtaTaskRecordDO updateObj) {
+ return update(updateObj, new LambdaUpdateWrapper()
+ .eq(IotOtaTaskRecordDO::getId, id)
+ .eq(IotOtaTaskRecordDO::getStatus, status));
+ }
+
+ default int updateByIdAndStatus(Long id, Collection whereStatuses,
+ IotOtaTaskRecordDO updateObj) {
+ return update(updateObj, new LambdaUpdateWrapper()
+ .eq(IotOtaTaskRecordDO::getId, id)
+ .in(IotOtaTaskRecordDO::getStatus, whereStatuses));
+ }
+
+ default void updateListByIdAndStatus(Collection ids, Collection whereStatuses,
+ IotOtaTaskRecordDO updateObj) {
+ update(updateObj, new LambdaUpdateWrapper()
+ .in(IotOtaTaskRecordDO::getId, ids)
+ .in(IotOtaTaskRecordDO::getStatus, whereStatuses));
+ }
+
+ default List selectListByDeviceIdAndStatus(Set deviceIds, Set statuses) {
+ return selectList(new LambdaQueryWrapperX()
+ .inIfPresent(IotOtaTaskRecordDO::getDeviceId, deviceIds)
+ .inIfPresent(IotOtaTaskRecordDO::getStatus, statuses));
+ }
+
+ default List selectListByDeviceIdAndStatus(Long deviceId, Set statuses) {
+ return selectList(new LambdaQueryWrapperX()
+ .eqIfPresent(IotOtaTaskRecordDO::getDeviceId, deviceId)
+ .inIfPresent(IotOtaTaskRecordDO::getStatus, statuses));
+ }
+
+ default List selectListByStatus(Integer status) {
+ return selectList(IotOtaTaskRecordDO::getStatus, status);
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java
new file mode 100644
index 0000000000..7c0c17d3bc
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.rule;
+
+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.framework.mybatis.core.util.MyBatisUtils;
+import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRulePageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * IoT 数据流转规则 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface IotDataRuleMapper extends BaseMapperX {
+
+ default PageResult selectPage(IotDataRulePageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(IotDataRuleDO::getName, reqVO.getName())
+ .eqIfPresent(IotDataRuleDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(IotDataRuleDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(IotDataRuleDO::getId));
+ }
+
+ default List selectListBySinkId(Long sinkId) {
+ return selectList(new LambdaQueryWrapperX()
+ .apply(MyBatisUtils.findInSet("sink_ids", sinkId)));
+ }
+
+ default List selectListByStatus(Integer status) {
+ return selectList(IotDataRuleDO::getStatus, status);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataSinkMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataSinkMapper.java
new file mode 100644
index 0000000000..e65001db86
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataSinkMapper.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.rule;
+
+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.rule.vo.data.sink.IotDataSinkPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * IoT 数据流转目的 Mapper
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface IotDataSinkMapper extends BaseMapperX {
+
+ default PageResult selectPage(IotDataSinkPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(IotDataSinkDO::getName, reqVO.getName())
+ .eqIfPresent(IotDataSinkDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(IotDataSinkDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(IotDataSinkDO::getId));
+ }
+
+ default List selectListByStatus(Integer status) {
+ return selectList(IotDataSinkDO::getStatus, status);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotSceneRuleMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotSceneRuleMapper.java
new file mode 100644
index 0000000000..4fd6490d15
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotSceneRuleMapper.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.rule;
+
+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.rule.vo.scene.IotSceneRulePageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * IoT 场景联动 Mapper
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface IotSceneRuleMapper extends BaseMapperX {
+
+ default PageResult selectPage(IotSceneRulePageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(IotSceneRuleDO::getName, reqVO.getName())
+ .likeIfPresent(IotSceneRuleDO::getDescription, reqVO.getDescription())
+ .eqIfPresent(IotSceneRuleDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(IotSceneRuleDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(IotSceneRuleDO::getId));
+ }
+
+ default List selectListByStatus(Integer status) {
+ return selectList(IotSceneRuleDO::getStatus, status);
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DeviceServerIdRedisDAO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DeviceServerIdRedisDAO.java
new file mode 100644
index 0000000000..cef78f3cff
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DeviceServerIdRedisDAO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.dal.redis.device;
+
+import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
+import jakarta.annotation.Resource;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+/**
+ * 设备关联的网关 serverId 的 Redis DAO
+ *
+ * @author 芋道源码
+ */
+@Repository
+public class DeviceServerIdRedisDAO {
+
+ @Resource
+ private StringRedisTemplate stringRedisTemplate;
+
+ public void update(Long deviceId, String serverId) {
+ stringRedisTemplate.opsForHash().put(RedisKeyConstants.DEVICE_SERVER_ID,
+ String.valueOf(deviceId), serverId);
+ }
+
+ public String get(Long deviceId) {
+ Object value = stringRedisTemplate.opsForHash().get(RedisKeyConstants.DEVICE_SERVER_ID,
+ String.valueOf(deviceId));
+ return value != null ? (String) value : null;
+ }
+
+}
\ 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/tdengine/IotDeviceMessageMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceMessageMapper.java
new file mode 100644
index 0000000000..b09895fd36
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceMessageMapper.java
@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.iot.dal.tdengine;
+
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
+import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS;
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 设备消息 {@link IotDeviceMessageDO} Mapper 接口
+ */
+@Mapper
+@TDengineDS
+@InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错
+public interface IotDeviceMessageMapper {
+
+ /**
+ * 创建设备消息超级表
+ */
+ void createSTable();
+
+ /**
+ * 查询设备消息表是否存在
+ *
+ * @return 存在则返回表名;不存在则返回 null
+ */
+ String showSTable();
+
+ /**
+ * 插入设备消息数据
+ *
+ * 如果子表不存在,会自动创建子表
+ *
+ * @param message 设备消息数据
+ */
+ void insert(IotDeviceMessageDO message);
+
+ /**
+ * 获得设备消息分页
+ *
+ * @param reqVO 分页查询条件
+ * @return 设备消息列表
+ */
+ IPage selectPage(IPage page,
+ @Param("reqVO") IotDeviceMessagePageReqVO reqVO);
+
+ /**
+ * 统计设备消息数量
+ *
+ * @param createTime 创建时间,如果为空,则统计所有消息数量
+ * @return 消息数量
+ */
+ Long selectCountByCreateTime(@Param("createTime") Long createTime);
+
+ /**
+ * 按照 requestIds 批量查询消息
+ *
+ * @param deviceId 设备编号
+ * @param requestIds 请求编号集合
+ * @param reply 是否回复消息
+ * @return 消息列表
+ */
+ List selectListByRequestIdsAndReply(@Param("deviceId") Long deviceId,
+ @Param("requestIds") Collection requestIds,
+ @Param("reply") Boolean reply);
+
+ /**
+ * 按照时间范围(小时),统计设备的消息数量
+ */
+ List