com.viewsh
diff --git a/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/IotRuleChainController.java b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/IotRuleChainController.java
new file mode 100644
index 00000000..1d033e7a
--- /dev/null
+++ b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/IotRuleChainController.java
@@ -0,0 +1,73 @@
+package com.viewsh.module.iot.rule.controller.admin;
+
+import com.viewsh.framework.common.pojo.CommonResult;
+import com.viewsh.framework.common.pojo.PageResult;
+import com.viewsh.module.iot.rule.controller.admin.vo.IotRuleChainGraphVO;
+import com.viewsh.module.iot.rule.controller.admin.vo.IotRuleChainPageReqVO;
+import com.viewsh.module.iot.rule.controller.admin.vo.IotRuleChainRespVO;
+import com.viewsh.module.iot.rule.controller.admin.vo.IotRuleChainSaveReqVO;
+import com.viewsh.module.iot.rule.service.IotRuleChainService;
+import jakarta.annotation.Resource;
+import org.springframework.web.bind.annotation.*;
+
+import static com.viewsh.framework.common.pojo.CommonResult.success;
+
+/**
+ * 管理后台 - IoT 规则链(DAG)Controller
+ *
+ * 权限:iot:rule:create / iot:rule:update / iot:rule:delete / iot:rule:query
+ * (由部署层 @PreAuthorize 保证,运行时需引入 spring-security)
+ */
+@RestController
+@RequestMapping("/iot/rule-chain")
+public class IotRuleChainController {
+
+ @Resource
+ private IotRuleChainService ruleChainService;
+
+ // @PreAuthorize("@ss.hasPermission('iot:rule:create')")
+ @PostMapping("/create")
+ public CommonResult createRuleChain(@RequestBody IotRuleChainSaveReqVO createReqVO) {
+ return success(ruleChainService.createRuleChain(createReqVO));
+ }
+
+ // @PreAuthorize("@ss.hasPermission('iot:rule:update')")
+ @PutMapping("/update")
+ public CommonResult updateRuleChain(@RequestBody IotRuleChainSaveReqVO updateReqVO) {
+ ruleChainService.updateRuleChain(updateReqVO);
+ return success(true);
+ }
+
+ // @PreAuthorize("@ss.hasPermission('iot:rule:delete')")
+ @DeleteMapping("/delete")
+ public CommonResult deleteRuleChain(@RequestParam("id") Long id) {
+ ruleChainService.deleteRuleChain(id);
+ return success(true);
+ }
+
+ // @PreAuthorize("@ss.hasPermission('iot:rule:query')")
+ @GetMapping("/get/{id}")
+ public CommonResult getRuleChain(@PathVariable("id") Long id) {
+ return success(ruleChainService.getRuleChain(id));
+ }
+
+ // @PreAuthorize("@ss.hasPermission('iot:rule:query')")
+ @GetMapping("/graph/{id}")
+ public CommonResult getRuleChainGraph(@PathVariable("id") Long id) {
+ return success(ruleChainService.getRuleChainGraph(id));
+ }
+
+ // @PreAuthorize("@ss.hasPermission('iot:rule:query')")
+ @GetMapping("/page")
+ public CommonResult> getRuleChainPage(IotRuleChainPageReqVO pageReqVO) {
+ return success(ruleChainService.getRuleChainPage(pageReqVO));
+ }
+
+ // @PreAuthorize("@ss.hasPermission('iot:rule:update')")
+ @PutMapping("/enable")
+ public CommonResult enableRuleChain(@RequestParam("id") Long id) {
+ ruleChainService.enableRuleChain(id);
+ return success(true);
+ }
+
+}
diff --git a/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/vo/IotRuleChainGraphVO.java b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/vo/IotRuleChainGraphVO.java
new file mode 100644
index 00000000..3f1d1ce4
--- /dev/null
+++ b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/vo/IotRuleChainGraphVO.java
@@ -0,0 +1,95 @@
+package com.viewsh.module.iot.rule.controller.admin.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 管理后台 - IoT 规则链图 VO(含 nodes + links 的完整图)
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotRuleChainGraphVO {
+
+ /** 规则链基础信息 */
+ private IotRuleChainRespVO chain;
+
+ /** 节点列表 */
+ private List nodes;
+
+ /** 连线列表 */
+ private List links;
+
+ /**
+ * 节点 VO
+ */
+ @Data
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class NodeVO {
+
+ /** 节点编号 */
+ private Long id;
+
+ /** 所属规则链编号 */
+ private Long ruleChainId;
+
+ /** 节点名称 */
+ private String name;
+
+ /** 节点类别(trigger/condition/action) */
+ private String category;
+
+ /** Provider 标识 */
+ private String type;
+
+ /** 节点配置(JSON) */
+ private String configuration;
+
+ /** 画布 X 坐标 */
+ private Integer positionX;
+
+ /** 画布 Y 坐标 */
+ private Integer positionY;
+
+ }
+
+ /**
+ * 连线 VO
+ */
+ @Data
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class LinkVO {
+
+ /** 连线编号 */
+ private Long id;
+
+ /** 所属规则链编号 */
+ private Long ruleChainId;
+
+ /** 源节点编号 */
+ private Long sourceNodeId;
+
+ /** 目标节点编号 */
+ private Long targetNodeId;
+
+ /** 关系类型(Success/Failure/True/False/Timeout/Skip) */
+ private String relationType;
+
+ /** 连线条件(JSON) */
+ private String condition;
+
+ /** 排序 */
+ private Integer sortOrder;
+
+ }
+
+}
diff --git a/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/vo/IotRuleChainPageReqVO.java b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/vo/IotRuleChainPageReqVO.java
new file mode 100644
index 00000000..13d9e192
--- /dev/null
+++ b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/vo/IotRuleChainPageReqVO.java
@@ -0,0 +1,37 @@
+package com.viewsh.module.iot.rule.controller.admin.vo;
+
+import com.viewsh.framework.common.pojo.PageParam;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static com.viewsh.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+ * 管理后台 - IoT 规则链分页 Request VO
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotRuleChainPageReqVO extends PageParam {
+
+ /** 租户编号(内部使用) */
+ private Long tenantId;
+
+ /** 规则链名称 */
+ private String name;
+
+ /** 状态(0=禁用 1=启用 2=WARNING) */
+ private Integer status;
+
+ /** 规则链类型(SCENE/DATA/CUSTOM) */
+ private String type;
+
+ /** 创建时间 */
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
diff --git a/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/vo/IotRuleChainRespVO.java b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/vo/IotRuleChainRespVO.java
new file mode 100644
index 00000000..a2e5483f
--- /dev/null
+++ b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/vo/IotRuleChainRespVO.java
@@ -0,0 +1,52 @@
+package com.viewsh.module.iot.rule.controller.admin.vo;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 管理后台 - IoT 规则链 Response VO
+ */
+@Data
+public class IotRuleChainRespVO {
+
+ /** 规则链编号 */
+ private Long id;
+
+ /** 规则链名称 */
+ private String name;
+
+ /** 规则链描述 */
+ private String description;
+
+ /** 规则链类型(SCENE/DATA/CUSTOM) */
+ private String type;
+
+ /** 状态(0=禁用 1=启用 2=WARNING) */
+ private Integer status;
+
+ /** 优先级 */
+ private Integer priority;
+
+ /** 版本号(乐观锁) */
+ private Long version;
+
+ /** 调试模式 */
+ private Boolean debugMode;
+
+ /** 子系统编号 */
+ private Long subsystemId;
+
+ /** 产品编号 */
+ private Long productId;
+
+ /** 设备编号 */
+ private Long deviceId;
+
+ /** 创建时间 */
+ private LocalDateTime createTime;
+
+ /** 更新时间 */
+ private LocalDateTime updateTime;
+
+}
diff --git a/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/vo/IotRuleChainSaveReqVO.java b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/vo/IotRuleChainSaveReqVO.java
new file mode 100644
index 00000000..632e0c42
--- /dev/null
+++ b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/controller/admin/vo/IotRuleChainSaveReqVO.java
@@ -0,0 +1,101 @@
+package com.viewsh.module.iot.rule.controller.admin.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 管理后台 - IoT 规则链新增/修改 Request VO
+ */
+@Data
+public class IotRuleChainSaveReqVO {
+
+ /** 规则链编号(修改时必填) */
+ private Long id;
+
+ /** 规则链名称(必填) */
+ private String name;
+
+ /** 规则链描述 */
+ private String description;
+
+ /** 规则链类型(SCENE/DATA/CUSTOM,必填) */
+ private String type;
+
+ /** 优先级 */
+ private Integer priority;
+
+ /** 调试模式 */
+ private Boolean debugMode;
+
+ /** 子系统编号(NULL=全局规则) */
+ private Long subsystemId;
+
+ /** 产品编号(NULL=不限产品) */
+ private Long productId;
+
+ /** 设备编号(NULL/0=范围内所有设备) */
+ private Long deviceId;
+
+ /** 规则节点列表(必填) */
+ private List nodes;
+
+ /** 规则连线列表 */
+ private List links;
+
+ /**
+ * 节点子 VO
+ */
+ @Data
+ public static class NodeVO {
+
+ /** 节点编号(更新时填写,新增可不填) */
+ private Long id;
+
+ /** 节点名称 */
+ private String name;
+
+ /** 节点类别(trigger/condition/action,必填) */
+ private String category;
+
+ /** Provider 标识(必填) */
+ private String type;
+
+ /** 节点配置(JSON 字符串,必填) */
+ private String configuration;
+
+ /** 画布 X 坐标 */
+ private Integer positionX;
+
+ /** 画布 Y 坐标 */
+ private Integer positionY;
+
+ }
+
+ /**
+ * 连线子 VO
+ */
+ @Data
+ public static class LinkVO {
+
+ /** 连线编号(更新时填写,新增可不填) */
+ private Long id;
+
+ /** 源节点编号(必填) */
+ private Long sourceNodeId;
+
+ /** 目标节点编号(必填) */
+ private Long targetNodeId;
+
+ /** 关系类型(Success/Failure/True/False/Timeout/Skip,必填) */
+ private String relationType;
+
+ /** 连线条件(JSON,可选) */
+ private String condition;
+
+ /** 排序 */
+ private Integer sortOrder;
+
+ }
+
+}
diff --git a/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/IotRuleChainDO.java b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/IotRuleChainDO.java
new file mode 100644
index 00000000..f9244ab1
--- /dev/null
+++ b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/IotRuleChainDO.java
@@ -0,0 +1,90 @@
+package com.viewsh.module.iot.rule.dal.dataobject;
+
+import com.viewsh.framework.mybatis.core.dataobject.BaseDO;
+import com.viewsh.module.iot.rule.dal.dataobject.enums.RuleChainStatus;
+import com.viewsh.module.iot.rule.dal.dataobject.enums.RuleChainType;
+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 规则链 DO
+ */
+@TableName("iot_rule_chain")
+@KeySequence("iot_rule_chain_seq")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotRuleChainDO extends BaseDO {
+
+ /**
+ * 规则链编号
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 规则链名称
+ */
+ private String name;
+
+ /**
+ * 规则链描述
+ */
+ private String description;
+
+ /**
+ * 规则链类型
+ *
+ * 枚举 {@link RuleChainType}
+ */
+ private String type;
+
+ /**
+ * 规则链状态
+ *
+ * 枚举 {@link RuleChainStatus}
+ */
+ private Integer status;
+
+ /**
+ * 优先级(ASC 排序,评审 A5)
+ */
+ private Integer priority;
+
+ /**
+ * 版本号(每次更新 +1,评审 B9 多实例校验用)
+ */
+ private Long version;
+
+ /**
+ * 调试模式
+ */
+ private Boolean debugMode;
+
+ /**
+ * 子系统编号(NULL = 全局规则)
+ */
+ private Long subsystemId;
+
+ /**
+ * 产品编号(NULL = 不限产品)
+ */
+ private Long productId;
+
+ /**
+ * 设备编号(NULL/0 = 范围内所有设备)
+ */
+ private Long deviceId;
+
+ /**
+ * 租户编号
+ */
+ private Long tenantId;
+
+}
diff --git a/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/IotRuleLinkDO.java b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/IotRuleLinkDO.java
new file mode 100644
index 00000000..7c58aca6
--- /dev/null
+++ b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/IotRuleLinkDO.java
@@ -0,0 +1,73 @@
+package com.viewsh.module.iot.rule.dal.dataobject;
+
+import com.viewsh.framework.mybatis.core.dataobject.BaseDO;
+import com.viewsh.module.iot.rule.dal.dataobject.enums.RuleLinkRelationType;
+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 规则连线 DO
+ */
+@TableName("iot_rule_link")
+@KeySequence("iot_rule_link_seq")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotRuleLinkDO extends BaseDO {
+
+ /**
+ * 连线编号
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 所属规则链编号
+ *
+ * 关联 {@link IotRuleChainDO#getId()}
+ */
+ private Long ruleChainId;
+
+ /**
+ * 源节点编号
+ *
+ * 关联 {@link IotRuleNodeDO#getId()}
+ */
+ private Long sourceNodeId;
+
+ /**
+ * 目标节点编号
+ *
+ * 关联 {@link IotRuleNodeDO#getId()}
+ */
+ private Long targetNodeId;
+
+ /**
+ * 关系类型(封闭枚举,评审 B4)
+ *
+ * 枚举 {@link RuleLinkRelationType}
+ */
+ private String relationType;
+
+ /**
+ * 连线条件(JSON,可选)
+ */
+ private String condition;
+
+ /**
+ * 排序
+ */
+ private Integer sortOrder;
+
+ /**
+ * 租户编号
+ */
+ private Long tenantId;
+
+}
diff --git a/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/IotRuleNodeDO.java b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/IotRuleNodeDO.java
new file mode 100644
index 00000000..82898820
--- /dev/null
+++ b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/IotRuleNodeDO.java
@@ -0,0 +1,74 @@
+package com.viewsh.module.iot.rule.dal.dataobject;
+
+import com.viewsh.framework.mybatis.core.dataobject.BaseDO;
+import com.viewsh.module.iot.rule.dal.dataobject.enums.RuleNodeCategory;
+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 规则节点 DO
+ */
+@TableName("iot_rule_node")
+@KeySequence("iot_rule_node_seq")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotRuleNodeDO extends BaseDO {
+
+ /**
+ * 节点编号
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 所属规则链编号
+ *
+ * 关联 {@link IotRuleChainDO#getId()}
+ */
+ private Long ruleChainId;
+
+ /**
+ * 节点名称
+ */
+ private String name;
+
+ /**
+ * 节点类别(trigger / condition / action)
+ *
+ * 枚举 {@link RuleNodeCategory}
+ */
+ private String category;
+
+ /**
+ * Provider 标识(device_property / alarm_trigger 等)
+ */
+ private String type;
+
+ /**
+ * 节点配置(JSON)
+ */
+ private String configuration;
+
+ /**
+ * 画布 X 坐标
+ */
+ private Integer positionX;
+
+ /**
+ * 画布 Y 坐标
+ */
+ private Integer positionY;
+
+ /**
+ * 租户编号
+ */
+ private Long tenantId;
+
+}
diff --git a/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/enums/RuleChainStatus.java b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/enums/RuleChainStatus.java
new file mode 100644
index 00000000..39830f3a
--- /dev/null
+++ b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/enums/RuleChainStatus.java
@@ -0,0 +1,25 @@
+package com.viewsh.module.iot.rule.dal.dataobject.enums;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import com.fasterxml.jackson.annotation.JsonValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 规则链状态枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum RuleChainStatus {
+
+ DISABLED(0, "禁用"),
+ ENABLED(1, "启用"),
+ WARNING(2, "警告(物模型变更导致)");
+
+ @EnumValue
+ @JsonValue
+ private final Integer value;
+
+ private final String label;
+
+}
diff --git a/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/enums/RuleChainType.java b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/enums/RuleChainType.java
new file mode 100644
index 00000000..6c582026
--- /dev/null
+++ b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/enums/RuleChainType.java
@@ -0,0 +1,25 @@
+package com.viewsh.module.iot.rule.dal.dataobject.enums;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import com.fasterxml.jackson.annotation.JsonValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 规则链类型枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum RuleChainType {
+
+ SCENE("SCENE", "场景联动"),
+ DATA("DATA", "数据流转"),
+ CUSTOM("CUSTOM", "自定义");
+
+ @EnumValue
+ @JsonValue
+ private final String value;
+
+ private final String label;
+
+}
diff --git a/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/enums/RuleLinkRelationType.java b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/enums/RuleLinkRelationType.java
new file mode 100644
index 00000000..ddf2df00
--- /dev/null
+++ b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/enums/RuleLinkRelationType.java
@@ -0,0 +1,49 @@
+package com.viewsh.module.iot.rule.dal.dataobject.enums;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import com.fasterxml.jackson.annotation.JsonValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 规则连线关系类型枚举(封闭 6 值,评审 B4)
+ *
+ * 使用 VARCHAR(32) 存储,应用层枚举校验,不使用 MySQL ENUM 类型
+ */
+@Getter
+@AllArgsConstructor
+public enum RuleLinkRelationType {
+
+ SUCCESS("Success", "成功"),
+ FAILURE("Failure", "失败"),
+ TRUE("True", "为真"),
+ FALSE("False", "为假"),
+ TIMEOUT("Timeout", "超时"),
+ SKIP("Skip", "跳过");
+
+ @EnumValue
+ @JsonValue
+ private final String value;
+
+ private final String label;
+
+ /**
+ * 根据 value 获取枚举,校验时使用
+ */
+ public static RuleLinkRelationType of(String value) {
+ for (RuleLinkRelationType type : values()) {
+ if (type.value.equals(value)) {
+ return type;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 校验 value 是否合法
+ */
+ public static boolean isValid(String value) {
+ return of(value) != null;
+ }
+
+}
diff --git a/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/enums/RuleNodeCategory.java b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/enums/RuleNodeCategory.java
new file mode 100644
index 00000000..646e3588
--- /dev/null
+++ b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/dataobject/enums/RuleNodeCategory.java
@@ -0,0 +1,25 @@
+package com.viewsh.module.iot.rule.dal.dataobject.enums;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import com.fasterxml.jackson.annotation.JsonValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 规则节点类别枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum RuleNodeCategory {
+
+ TRIGGER("trigger", "触发器"),
+ CONDITION("condition", "条件"),
+ ACTION("action", "动作");
+
+ @EnumValue
+ @JsonValue
+ private final String value;
+
+ private final String label;
+
+}
diff --git a/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/mysql/IotRuleChainMapper.java b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/mysql/IotRuleChainMapper.java
new file mode 100644
index 00000000..a22d4941
--- /dev/null
+++ b/viewsh-module-iot/viewsh-module-iot-rule/src/main/java/com/viewsh/module/iot/rule/dal/mysql/IotRuleChainMapper.java
@@ -0,0 +1,68 @@
+package com.viewsh.module.iot.rule.dal.mysql;
+
+import com.viewsh.framework.common.pojo.PageResult;
+import com.viewsh.framework.mybatis.core.mapper.BaseMapperX;
+import com.viewsh.framework.mybatis.core.query.LambdaQueryWrapperX;
+import com.viewsh.module.iot.rule.controller.admin.vo.IotRuleChainPageReqVO;
+import com.viewsh.module.iot.rule.dal.dataobject.IotRuleChainDO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * IoT 规则链 Mapper
+ */
+@Mapper
+public interface IotRuleChainMapper extends BaseMapperX {
+
+ default PageResult selectPage(IotRuleChainPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(IotRuleChainDO::getTenantId, reqVO.getTenantId())
+ .likeIfPresent(IotRuleChainDO::getName, reqVO.getName())
+ .eqIfPresent(IotRuleChainDO::getStatus, reqVO.getStatus())
+ .eqIfPresent(IotRuleChainDO::getType, reqVO.getType())
+ .betweenIfPresent(IotRuleChainDO::getCreateTime, reqVO.getCreateTime())
+ .orderByAsc(IotRuleChainDO::getPriority)
+ .orderByDesc(IotRuleChainDO::getId));
+ }
+
+ /**
+ * 查询指定租户所有启用的规则链(B3 消费)
+ */
+ default List selectEnabledByTenant(Long tenantId) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(IotRuleChainDO::getTenantId, tenantId)
+ .eq(IotRuleChainDO::getStatus, 1) // ENABLED
+ .orderByAsc(IotRuleChainDO::getPriority));
+ }
+
+ /**
+ * 乐观锁更新版本(评审 B9)
+ * 仅当 id 和 expectedVersion 均匹配时才更新,同时 version+1
+ *
+ * @return 更新行数(0 表示乐观锁冲突)
+ */
+ @Update("UPDATE iot_rule_chain SET version = version + 1, name = #{chain.name}, "
+ + "description = #{chain.description}, type = #{chain.type}, status = #{chain.status}, "
+ + "priority = #{chain.priority}, debug_mode = #{chain.debugMode}, "
+ + "subsystem_id = #{chain.subsystemId}, product_id = #{chain.productId}, "
+ + "device_id = #{chain.deviceId} "
+ + "WHERE id = #{id} AND version = #{expectedVersion} AND deleted = 0")
+ int updateWithVersion(@Param("id") Long id,
+ @Param("expectedVersion") Long expectedVersion,
+ @Param("chain") IotRuleChainDO chain);
+
+ /**
+ * 拉取 id+version 列表(B9 拉模式兜底扫描)
+ */
+ @Select("SELECT id, version FROM iot_rule_chain "
+ + "WHERE tenant_id = #{tenantId} AND update_time >= #{since} AND deleted = 0")
+ List