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
index 68a8fd699b..b42e1c0a42 100644
--- 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
@@ -17,6 +17,8 @@ import lombok.Data;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = IotDataSinkHttpConfig.class, name = "1"),
+ @JsonSubTypes.Type(value = IotDataSinkTcpConfig.class, name = "2"),
+ @JsonSubTypes.Type(value = IotDataSinkWebSocketConfig.class, name = "3"),
@JsonSubTypes.Type(value = IotDataSinkMqttConfig.class, name = "10"),
@JsonSubTypes.Type(value = IotDataSinkRedisConfig.class, name = "21"),
@JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"),
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkTcpConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkTcpConfig.java
new file mode 100644
index 0000000000..3d96f11ceb
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkTcpConfig.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import lombok.Data;
+
+/**
+ * IoT TCP 配置 {@link IotAbstractDataSinkConfig} 实现类
+ *
+ * @author HUIHUI
+ */
+@Data
+public class IotDataSinkTcpConfig extends IotAbstractDataSinkConfig {
+
+ /**
+ * TCP 服务器地址
+ */
+ private String host;
+
+ /**
+ * TCP 服务器端口
+ */
+ private Integer port;
+
+ /**
+ * 连接超时时间(毫秒)
+ */
+ private Integer connectTimeoutMs = 5000;
+
+ /**
+ * 读取超时时间(毫秒)
+ */
+ private Integer readTimeoutMs = 10000;
+
+ /**
+ * 是否启用 SSL
+ */
+ private Boolean ssl = false;
+
+ /**
+ * SSL 证书路径(当 ssl=true 时需要)
+ */
+ private String sslCertPath;
+
+ /**
+ * 数据格式:JSON 或 BINARY
+ */
+ private String dataFormat = "JSON";
+
+ /**
+ * 心跳间隔时间(毫秒),0 表示不启用心跳
+ */
+ private Long heartbeatIntervalMs = 30000L;
+
+ /**
+ * 重连间隔时间(毫秒)
+ */
+ private Long reconnectIntervalMs = 5000L;
+
+ /**
+ * 最大重连次数
+ */
+ private Integer maxReconnectAttempts = 3;
+
+}
\ 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/config/IotDataSinkWebSocketConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkWebSocketConfig.java
new file mode 100644
index 0000000000..f1b7e86d86
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkWebSocketConfig.java
@@ -0,0 +1,87 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import lombok.Data;
+
+/**
+ * IoT WebSocket 配置 {@link IotAbstractDataSinkConfig} 实现类
+ *
+ * 配置设备消息通过 WebSocket 协议发送到外部 WebSocket 服务器
+ * 支持 WebSocket (ws://) 和 WebSocket Secure (wss://) 连接
+ *
+ * @author HUIHUI
+ */
+@Data
+public class IotDataSinkWebSocketConfig extends IotAbstractDataSinkConfig {
+
+ /**
+ * WebSocket 服务器地址
+ * 例如:ws://localhost:8080/ws 或 wss://example.com/ws
+ */
+ private String serverUrl;
+
+ /**
+ * 连接超时时间(毫秒)
+ */
+ private Integer connectTimeoutMs = 5000;
+
+ /**
+ * 发送超时时间(毫秒)
+ */
+ private Integer sendTimeoutMs = 10000;
+
+ /**
+ * 心跳间隔时间(毫秒),0 表示不启用心跳
+ */
+ private Long heartbeatIntervalMs = 30000L;
+
+ /**
+ * 心跳消息内容(JSON 格式)
+ */
+ private String heartbeatMessage = "{\"type\":\"heartbeat\"}";
+
+ /**
+ * 子协议列表(逗号分隔)
+ */
+ private String subprotocols;
+
+ /**
+ * 自定义请求头(JSON 格式)
+ */
+ private String customHeaders;
+
+ /**
+ * 是否启用 SSL 证书验证(仅对 wss:// 生效)
+ */
+ private Boolean verifySslCert = true;
+
+ /**
+ * 数据格式:JSON 或 TEXT
+ */
+ private String dataFormat = "JSON";
+
+ /**
+ * 重连间隔时间(毫秒)
+ */
+ private Long reconnectIntervalMs = 5000L;
+
+ /**
+ * 最大重连次数
+ */
+ private Integer maxReconnectAttempts = 3;
+
+ /**
+ * 是否启用压缩
+ */
+ private Boolean enableCompression = false;
+
+ /**
+ * 消息发送重试次数
+ */
+ private Integer sendRetryCount = 1;
+
+ /**
+ * 消息发送重试间隔(毫秒)
+ */
+ private Long sendRetryIntervalMs = 1000L;
+
+}
\ 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/redis/RedisKeyConstants.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java
index 1187677e54..c8041a673c 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java
@@ -76,4 +76,12 @@ public interface RedisKeyConstants {
*/
String DATA_SINK = "iot:data_sink";
+ /**
+ * 场景联动规则的数据缓存,使用 Spring Cache 操作
+ *
+ * KEY 格式:scene_rule_list_${productId}_${deviceId}
+ * VALUE 数据类型:String 数组(JSON),即 {@link cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO} 列表
+ */
+ String SCENE_RULE_LIST = "iot:scene_rule_list";
+
}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleAction.java
new file mode 100644
index 0000000000..e9810eb08d
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleAction.java
@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.module.iot.service.rule.data.action;
+
+import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkTcpConfig;
+import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
+import cn.iocoder.yudao.module.iot.service.rule.data.action.tcp.IotTcpClient;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.time.Duration;
+
+/**
+ * TCP 的 {@link IotDataRuleAction} 实现类
+ *
+ * 负责将设备消息发送到外部 TCP 服务器
+ * 支持普通 TCP 和 SSL TCP 连接,支持 JSON 和 BINARY 数据格式
+ * 使用连接池管理 TCP 连接,提高性能和资源利用率
+ *
+ * @author HUIHUI
+ */
+@Component
+@Slf4j
+public class IotTcpDataRuleAction extends
+ IotDataRuleCacheableAction {
+
+ private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(5);
+ private static final Duration SEND_TIMEOUT = Duration.ofSeconds(10);
+
+ @Override
+ public Integer getType() {
+ return IotDataSinkTypeEnum.TCP.getType();
+ }
+
+ @Override
+ protected IotTcpClient initProducer(IotDataSinkTcpConfig config) throws Exception {
+ // 1. 参数校验
+ if (config.getHost() == null || config.getHost().trim().isEmpty()) {
+ throw new IllegalArgumentException("TCP 服务器地址不能为空");
+ }
+ if (config.getPort() == null || config.getPort() <= 0 || config.getPort() > 65535) {
+ throw new IllegalArgumentException("TCP 服务器端口无效");
+ }
+
+ // 2. 创建 TCP 客户端
+ IotTcpClient tcpClient = new IotTcpClient(
+ config.getHost(),
+ config.getPort(),
+ config.getConnectTimeoutMs(),
+ config.getReadTimeoutMs(),
+ config.getSsl(),
+ config.getSslCertPath(),
+ config.getDataFormat()
+ );
+
+ // 3. 连接服务器
+ tcpClient.connect();
+
+ log.info("[initProducer][TCP 客户端创建并连接成功,服务器: {}:{},SSL: {},数据格式: {}]",
+ config.getHost(), config.getPort(), config.getSsl(), config.getDataFormat());
+
+ return tcpClient;
+ }
+
+ @Override
+ protected void closeProducer(IotTcpClient producer) throws Exception {
+ if (producer != null) {
+ producer.close();
+ }
+ }
+
+ @Override
+ protected void execute(IotDeviceMessage message, IotDataSinkTcpConfig config) throws Exception {
+ try {
+ // 1. 获取或创建 TCP 客户端
+ IotTcpClient tcpClient = getProducer(config);
+
+ // 2. 检查连接状态,如果断开则重新连接
+ if (!tcpClient.isConnected()) {
+ log.warn("[execute][TCP 连接已断开,尝试重新连接,服务器: {}:{}]", config.getHost(), config.getPort());
+ tcpClient.connect();
+ }
+
+ // 3. 发送消息并等待结果
+ tcpClient.sendMessage(message);
+
+ // 4. 记录发送成功日志
+ log.info("[execute][message({}) config({}) 发送成功,TCP 服务器: {}:{}]",
+ message, config, config.getHost(), config.getPort());
+
+ } catch (Exception e) {
+ log.error("[execute][message({}) config({}) 发送失败,TCP 服务器: {}:{}]",
+ message, config, config.getHost(), config.getPort(), e);
+ throw 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/service/rule/data/action/tcp/IotTcpClient.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/tcp/IotTcpClient.java
new file mode 100644
index 0000000000..052c43ed4c
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/tcp/IotTcpClient.java
@@ -0,0 +1,184 @@
+package cn.iocoder.yudao.module.iot.service.rule.data.action.tcp;
+
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.net.ssl.SSLSocketFactory;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * IoT TCP 客户端
+ *
+ * 负责与外部 TCP 服务器建立连接并发送设备消息
+ * 支持 JSON 和 BINARY 两种数据格式,支持 SSL 加密连接
+ *
+ * @author HUIHUI
+ */
+@Slf4j
+public class IotTcpClient {
+
+ private final String host;
+ private final Integer port;
+ private final Integer connectTimeoutMs;
+ private final Integer readTimeoutMs;
+ private final Boolean ssl;
+ private final String sslCertPath;
+ private final String dataFormat;
+
+ private Socket socket;
+ private OutputStream outputStream;
+ private BufferedReader reader;
+ private final AtomicBoolean connected = new AtomicBoolean(false);
+
+ public IotTcpClient(String host, Integer port, Integer connectTimeoutMs, Integer readTimeoutMs,
+ Boolean ssl, String sslCertPath, String dataFormat) {
+ this.host = host;
+ this.port = port;
+ this.connectTimeoutMs = connectTimeoutMs != null ? connectTimeoutMs : 5000;
+ this.readTimeoutMs = readTimeoutMs != null ? readTimeoutMs : 10000;
+ this.ssl = ssl != null ? ssl : false;
+ this.sslCertPath = sslCertPath;
+ this.dataFormat = dataFormat != null ? dataFormat : "JSON";
+ }
+
+ /**
+ * 连接到 TCP 服务器
+ */
+ public void connect() throws Exception {
+ if (connected.get()) {
+ log.warn("[connect][TCP 客户端已经连接,无需重复连接]");
+ return;
+ }
+
+ try {
+ if (ssl) {
+ // SSL 连接
+ SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
+ socket = sslSocketFactory.createSocket();
+ } else {
+ // 普通连接
+ socket = new Socket();
+ }
+
+ // 连接服务器
+ socket.connect(new InetSocketAddress(host, port), connectTimeoutMs);
+ socket.setSoTimeout(readTimeoutMs);
+
+ // 获取输入输出流
+ outputStream = socket.getOutputStream();
+ reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
+
+ connected.set(true);
+ log.info("[connect][TCP 客户端连接成功,服务器地址: {}:{}]", host, port);
+
+ } catch (Exception e) {
+ close();
+ log.error("[connect][TCP 客户端连接失败,服务器地址: {}:{}]", host, port, e);
+ throw e;
+ }
+ }
+
+ /**
+ * 发送设备消息
+ *
+ * @param message 设备消息
+ * @throws Exception 发送异常
+ */
+ public void sendMessage(IotDeviceMessage message) throws Exception {
+ if (!connected.get()) {
+ throw new IllegalStateException("TCP 客户端未连接");
+ }
+
+ try {
+ String messageData;
+ if ("JSON".equalsIgnoreCase(dataFormat)) {
+ // JSON 格式
+ messageData = JsonUtils.toJsonString(message);
+ } else {
+ // BINARY 格式(这里简化为字符串,实际可能需要自定义二进制协议)
+ messageData = message.toString();
+ }
+
+ // 发送消息
+ outputStream.write(messageData.getBytes(StandardCharsets.UTF_8));
+ outputStream.write('\n'); // 添加换行符作为消息分隔符
+ outputStream.flush();
+
+ log.debug("[sendMessage][发送消息成功,设备 ID: {},消息长度: {}]",
+ message.getDeviceId(), messageData.length());
+
+ } catch (Exception e) {
+ log.error("[sendMessage][发送消息失败,设备 ID: {}]", message.getDeviceId(), e);
+ throw e;
+ }
+ }
+
+ /**
+ * 关闭连接
+ */
+ public void close() {
+ if (!connected.get()) {
+ return;
+ }
+
+ try {
+ // 关闭资源
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ log.warn("[close][关闭输入流失败]", e);
+ }
+ }
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ log.warn("[close][关闭输出流失败]", e);
+ }
+ }
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ log.warn("[close][关闭 Socket 失败]", e);
+ }
+ }
+
+ connected.set(false);
+ log.info("[close][TCP 客户端连接已关闭,服务器地址: {}:{}]", host, port);
+
+ } catch (Exception e) {
+ log.error("[close][关闭 TCP 客户端连接异常]", e);
+ }
+ }
+
+ /**
+ * 检查连接状态
+ *
+ * @return 是否已连接
+ */
+ public boolean isConnected() {
+ return connected.get() && socket != null && !socket.isClosed();
+ }
+
+ @Override
+ public String toString() {
+ return "IotTcpClient{" +
+ "host='" + host + '\'' +
+ ", port=" + port +
+ ", ssl=" + ssl +
+ ", dataFormat='" + dataFormat + '\'' +
+ ", connected=" + connected.get() +
+ '}';
+ }
+
+}
\ 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/rule/scene/IotSceneRuleServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java
index c631e34586..a29ff98616 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java
@@ -16,6 +16,7 @@ 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.rule.IotSceneRuleDO;
import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper;
+import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
@@ -24,6 +25,8 @@ import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatche
import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotSceneRuleTimerHandler;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -60,6 +63,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
private IotSceneRuleTimerHandler timerHandler;
@Override
+ @CacheEvict(value = RedisKeyConstants.SCENE_RULE_LIST, allEntries = true)
public Long createSceneRule(IotSceneRuleSaveReqVO createReqVO) {
IotSceneRuleDO sceneRule = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class);
sceneRuleMapper.insert(sceneRule);
@@ -71,6 +75,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
}
@Override
+ @CacheEvict(value = RedisKeyConstants.SCENE_RULE_LIST, allEntries = true)
public void updateSceneRule(IotSceneRuleSaveReqVO updateReqVO) {
// 校验存在
validateSceneRuleExists(updateReqVO.getId());
@@ -83,6 +88,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
}
@Override
+ @CacheEvict(value = RedisKeyConstants.SCENE_RULE_LIST, allEntries = true)
public void updateSceneRuleStatus(Long id, Integer status) {
// 1. 校验存在
validateSceneRuleExists(id);
@@ -105,6 +111,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
}
@Override
+ @CacheEvict(value = RedisKeyConstants.SCENE_RULE_LIST, allEntries = true)
public void deleteSceneRule(Long id) {
// 1. 校验存在
validateSceneRuleExists(id);
@@ -149,15 +156,12 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
return sceneRuleMapper.selectListByStatus(status);
}
- // TODO @puhui999:缓存待实现
@Override
+ @Cacheable(value = RedisKeyConstants.SCENE_RULE_LIST, key = "#productId + '_' + #deviceId ")
@TenantIgnore // 忽略租户隔离:因为 IotSceneRuleMessageHandler 调用时,一般未传递租户,所以需要忽略
public List getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId) {
// 1. 查询启用状态的规则场景
- // TODO @puhui999:这里查询 enable 的;
- List list = sceneRuleMapper.selectList();
- List enabledList = filterList(list,
- sceneRule -> CommonStatusEnum.isEnable(sceneRule.getStatus()));
+ List enabledList = sceneRuleMapper.selectList(IotSceneRuleDO::getStatus, CommonStatusEnum.ENABLE.getStatus());
// 2. 根据 productKey 和 deviceName 进行匹配
return filterList(enabledList, sceneRule -> {
@@ -190,9 +194,10 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
@Override
public void executeSceneRuleByDevice(IotDeviceMessage message) {
- // TODO @puhui999:这里的 tenantId,通过设备获取;
- TenantUtils.execute(message.getTenantId(), () -> {
- // 1. 获得设备匹配的规则场景
+ // 1.1 这里的 tenantId,通过设备获取;
+ IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId());
+ TenantUtils.execute(device.getTenantId(), () -> {
+ // 1.2 获得设备匹配的规则场景
List sceneRules = getMatchedSceneRuleListByMessage(message);
if (CollUtil.isEmpty(sceneRules)) {
return;
@@ -238,14 +243,14 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
// 1. 匹配设备
// TODO 缓存 @puhui999:可能需要 getSelf()
// 1.1 通过 deviceId 获取设备信息
- IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId());
+ IotDeviceDO device = getSelf().deviceService.getDeviceFromCache(message.getDeviceId());
if (device == null) {
log.warn("[getMatchedSceneRuleListByMessage][设备({}) 不存在]", message.getDeviceId());
return List.of();
}
// 1.2 通过 productId 获取产品信息
- IotProductDO product = productService.getProductFromCache(device.getProductId());
+ IotProductDO product = getSelf().productService.getProductFromCache(device.getProductId());
if (product == null) {
log.warn("[getMatchedSceneRuleListByMessage][产品({}) 不存在]", device.getProductId());
return List.of();
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcher.java
similarity index 98%
rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java
rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcher.java
index cb85f36203..2083bebac9 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcher.java
@@ -16,7 +16,6 @@ import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
-// TODO @puhui999:是不是 IoT 的前缀,都加下哈;
/**
* 当前时间条件匹配器:处理时间相关的子条件匹配逻辑
*
@@ -24,7 +23,7 @@ import java.util.List;
*/
@Component
@Slf4j
-public class CurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher {
+public class IotCurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher {
/**
* 时间格式化器 - HH:mm:ss
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcher.java
similarity index 96%
rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java
rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcher.java
index f4316c0b5d..c130c55438 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcher.java
@@ -15,7 +15,7 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatcher {
+public class IotDevicePropertyConditionMatcher implements IotSceneRuleConditionMatcher {
@Override
public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcher.java
similarity index 96%
rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java
rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcher.java
index ceb1ee8c8b..232812270f 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcher.java
@@ -13,7 +13,7 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher {
+public class IotDeviceStateConditionMatcher implements IotSceneRuleConditionMatcher {
@Override
public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcher.java
similarity index 97%
rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java
rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcher.java
index 2a843d1c2c..825b5eae1d 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcher.java
@@ -15,7 +15,7 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher {
+public class IotDeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher {
@Override
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcher.java
similarity index 97%
rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java
rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcher.java
index fdaf68e3f1..27cb02a1a5 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcher.java
@@ -14,7 +14,7 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatcher {
+public class IotDevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatcher {
@Override
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java
similarity index 95%
rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java
rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java
index 2c357fb1e2..b5fa0330dc 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java
@@ -14,7 +14,7 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher {
+public class IotDeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher {
@Override
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
@@ -44,7 +44,7 @@ public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMat
// 2. 对于服务调用触发器,通常只需要匹配服务标识符即可
// 不需要检查操作符和值,因为服务调用本身就是触发条件
- // TODO @puhui999: 服务调用时校验输入参数是否匹配条件
+ // TODO @puhui999: 服务调用时校验输入参数是否匹配条件?
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
return true;
}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcher.java
similarity index 97%
rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java
rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcher.java
index b51716f4e3..6b8c73a501 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcher.java
@@ -14,7 +14,7 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher {
+public class IotDeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher {
@Override
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcher.java
similarity index 96%
rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java
rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcher.java
index 34cc59507d..f980c2471b 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcher.java
@@ -16,7 +16,7 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class TimerTriggerMatcher implements IotSceneRuleTriggerMatcher {
+public class IotTimerTriggerMatcher implements IotSceneRuleTriggerMatcher {
@Override
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleActionTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleActionTest.java
new file mode 100644
index 0000000000..3a7ee1cf73
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleActionTest.java
@@ -0,0 +1,161 @@
+package cn.iocoder.yudao.module.iot.service.rule.data.action;
+
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkTcpConfig;
+import cn.iocoder.yudao.module.iot.service.rule.data.action.tcp.IotTcpClient;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link IotTcpDataRuleAction} 的单元测试
+ *
+ * @author HUIHUI
+ */
+class IotTcpDataRuleActionTest {
+
+ private IotTcpDataRuleAction tcpDataRuleAction;
+
+ @Mock
+ private IotTcpClient mockTcpClient;
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ tcpDataRuleAction = new IotTcpDataRuleAction();
+ }
+
+ @Test
+ void testGetType() {
+ // 准备参数
+ Integer expectedType = 2; // 数据接收类型枚举中 TCP 类型的值
+
+ // 调用方法
+ Integer actualType = tcpDataRuleAction.getType();
+
+ // 断言结果
+ assertEquals(expectedType, actualType);
+ }
+
+ @Test
+ void testInitProducer_Success() throws Exception {
+ // 准备参数
+ IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
+ config.setHost("localhost");
+ config.setPort(8080);
+ config.setDataFormat("JSON");
+ config.setSsl(false);
+
+ // 调用方法 & 断言结果
+ // 此测试需要实际的 TCP 连接,在单元测试中可能不可用
+ // 目前我们只验证配置是否有效
+ assertNotNull(config.getHost());
+ assertTrue(config.getPort() > 0 && config.getPort() <= 65535);
+ }
+
+ @Test
+ void testInitProducer_InvalidHost() {
+ // 准备参数
+ IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
+ config.setHost("");
+ config.setPort(8080);
+
+ // 调用方法 & 断言结果
+ IotTcpDataRuleAction action = new IotTcpDataRuleAction();
+
+ // 测试验证逻辑(通常在 initProducer 方法中)
+ assertThrows(IllegalArgumentException.class, () -> {
+ if (config.getHost() == null || config.getHost().trim().isEmpty()) {
+ throw new IllegalArgumentException("TCP 服务器地址不能为空");
+ }
+ });
+ }
+
+ @Test
+ void testInitProducer_InvalidPort() {
+ // 准备参数
+ IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
+ config.setHost("localhost");
+ config.setPort(-1);
+
+ // 调用方法 & 断言结果
+ assertThrows(IllegalArgumentException.class, () -> {
+ if (config.getPort() == null || config.getPort() <= 0 || config.getPort() > 65535) {
+ throw new IllegalArgumentException("TCP 服务器端口无效");
+ }
+ });
+ }
+
+ @Test
+ void testCloseProducer() throws Exception {
+ // 准备参数
+ IotTcpClient client = mock(IotTcpClient.class);
+
+ // 调用方法
+ tcpDataRuleAction.closeProducer(client);
+
+ // 断言结果
+ verify(client, times(1)).close();
+ }
+
+ @Test
+ void testExecute_WithValidConfig() {
+ // 准备参数
+ IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.report",
+ "{\"temperature\": 25.5, \"humidity\": 60}");
+
+ IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
+ config.setHost("localhost");
+ config.setPort(8080);
+ config.setDataFormat("JSON");
+
+ // 调用方法 & 断言结果
+ // 通常这需要实际的 TCP 连接
+ // 在单元测试中,我们只验证输入参数
+ assertNotNull(message);
+ assertNotNull(config);
+ assertNotNull(config.getHost());
+ assertTrue(config.getPort() > 0);
+ }
+
+ @Test
+ void testConfig_DefaultValues() {
+ // 准备参数
+ IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
+
+ // 调用方法 & 断言结果
+ // 验证默认值
+ assertEquals("JSON", config.getDataFormat());
+ assertEquals(5000, config.getConnectTimeoutMs());
+ assertEquals(10000, config.getReadTimeoutMs());
+ assertEquals(false, config.getSsl());
+ assertEquals(30000L, config.getHeartbeatIntervalMs());
+ assertEquals(5000L, config.getReconnectIntervalMs());
+ assertEquals(3, config.getMaxReconnectAttempts());
+ }
+
+ @Test
+ void testMessageSerialization() {
+ // 准备参数
+ IotDeviceMessage message = IotDeviceMessage.builder()
+ .deviceId(123L)
+ .method("thing.property.report")
+ .params("{\"temperature\": 25.5}")
+ .build();
+
+ // 调用方法
+ String json = JsonUtils.toJsonString(message);
+
+ // 断言结果
+ assertNotNull(json);
+ assertTrue(json.contains("\"deviceId\":123"));
+ assertTrue(json.contains("\"method\":\"thing.property.report\""));
+ assertTrue(json.contains("\"temperature\":25.5"));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotBaseConditionMatcherTest.java
similarity index 88%
rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java
rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotBaseConditionMatcherTest.java
index b53e48da3a..5be63b57d2 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotBaseConditionMatcherTest.java
@@ -6,7 +6,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
-// TODO @puhui999:建议改成 IotBaseConditionMatcherTest
/**
* Matcher 测试基类
* 提供通用的 Spring 测试配置
@@ -14,7 +13,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
* @author HUIHUI
*/
@SpringJUnitConfig
-public abstract class BaseMatcherTest {
+public abstract class IotBaseConditionMatcherTest {
/**
* 注入一下 SpringUtil,解析 EL 表达式时需要
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcherTest.java
similarity index 96%
rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java
rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcherTest.java
index f14da99a56..586d948cd0 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcherTest.java
@@ -4,7 +4,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
-import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
+import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -16,17 +16,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
import static org.junit.jupiter.api.Assertions.*;
/**
- * {@link CurrentTimeConditionMatcher} 的单元测试
+ * {@link IotCurrentTimeConditionMatcher} 的单元测试
*
* @author HUIHUI
*/
-public class CurrentTimeConditionMatcherTest extends BaseMatcherTest {
+public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherTest {
- private CurrentTimeConditionMatcher matcher;
+ private IotCurrentTimeConditionMatcher matcher;
@BeforeEach
public void setUp() {
- matcher = new CurrentTimeConditionMatcher();
+ matcher = new IotCurrentTimeConditionMatcher();
}
@Test
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcherTest.java
similarity index 97%
rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java
rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcherTest.java
index ddfebc66be..5a40995567 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcherTest.java
@@ -4,7 +4,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
-import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
+import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -15,17 +15,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId
import static org.junit.jupiter.api.Assertions.*;
/**
- * {@link DevicePropertyConditionMatcher} 的单元测试
+ * {@link IotDevicePropertyConditionMatcher} 的单元测试
*
* @author HUIHUI
*/
-public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
+public class IotDevicePropertyConditionMatcherTest extends IotBaseConditionMatcherTest {
- private DevicePropertyConditionMatcher matcher;
+ private IotDevicePropertyConditionMatcher matcher;
@BeforeEach
public void setUp() {
- matcher = new DevicePropertyConditionMatcher();
+ matcher = new IotDevicePropertyConditionMatcher();
}
@Test
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java
similarity index 97%
rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java
rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java
index d54575ba41..da59077a6e 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java
@@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
-import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
+import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -14,17 +14,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
import static org.junit.jupiter.api.Assertions.*;
/**
- * {@link DeviceStateConditionMatcher} 的单元测试
+ * {@link IotDeviceStateConditionMatcher} 的单元测试
*
* @author HUIHUI
*/
-public class DeviceStateConditionMatcherTest extends BaseMatcherTest {
+public class IotDeviceStateConditionMatcherTest extends IotBaseConditionMatcherTest {
- private DeviceStateConditionMatcher matcher;
+ private IotDeviceStateConditionMatcher matcher;
@BeforeEach
public void setUp() {
- matcher = new DeviceStateConditionMatcher();
+ matcher = new IotDeviceStateConditionMatcher();
}
@Test
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcherTest.java
similarity index 97%
rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java
rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcherTest.java
index 4569d439cc..9cf51421fe 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcherTest.java
@@ -5,7 +5,7 @@ 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.rule.IotSceneRuleDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
-import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
+import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -18,17 +18,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
import static org.junit.jupiter.api.Assertions.*;
/**
- * {@link DeviceEventPostTriggerMatcher} 的单元测试
+ * {@link IotDeviceEventPostTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
-public class DeviceEventPostTriggerMatcherTest extends BaseMatcherTest {
+public class IotDeviceEventPostTriggerMatcherTest extends IotBaseConditionMatcherTest {
- private DeviceEventPostTriggerMatcher matcher;
+ private IotDeviceEventPostTriggerMatcher matcher;
@BeforeEach
public void setUp() {
- matcher = new DeviceEventPostTriggerMatcher();
+ matcher = new IotDeviceEventPostTriggerMatcher();
}
@Test
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcherTest.java
similarity index 96%
rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java
rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcherTest.java
index eb191eb927..fb155763aa 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcherTest.java
@@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
-import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
+import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -20,17 +20,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
import static org.junit.jupiter.api.Assertions.*;
/**
- * {@link DevicePropertyPostTriggerMatcher} 的单元测试
+ * {@link IotDevicePropertyPostTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
-public class DevicePropertyPostTriggerMatcherTest extends BaseMatcherTest {
+public class IotDevicePropertyPostTriggerMatcherTest extends IotBaseConditionMatcherTest {
- private DevicePropertyPostTriggerMatcher matcher;
+ private IotDevicePropertyPostTriggerMatcher matcher;
@BeforeEach
public void setUp() {
- matcher = new DevicePropertyPostTriggerMatcher();
+ matcher = new IotDevicePropertyPostTriggerMatcher();
}
@Test
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcherTest.java
similarity index 97%
rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java
rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcherTest.java
index cfa36605f8..a515f1268e 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcherTest.java
@@ -5,7 +5,7 @@ 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.rule.IotSceneRuleDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
-import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
+import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -18,17 +18,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
import static org.junit.jupiter.api.Assertions.*;
/**
- * {@link DeviceServiceInvokeTriggerMatcher} 的单元测试
+ * {@link IotDeviceServiceInvokeTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
-public class DeviceServiceInvokeTriggerMatcherTest extends BaseMatcherTest {
+public class IotDeviceServiceInvokeTriggerMatcherTest extends IotBaseConditionMatcherTest {
- private DeviceServiceInvokeTriggerMatcher matcher;
+ private IotDeviceServiceInvokeTriggerMatcher matcher;
@BeforeEach
public void setUp() {
- matcher = new DeviceServiceInvokeTriggerMatcher();
+ matcher = new IotDeviceServiceInvokeTriggerMatcher();
}
@Test
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java
similarity index 92%
rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java
rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java
index bee6e072e4..2e8b17a353 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java
@@ -1,42 +1,30 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
-import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
+import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static org.junit.jupiter.api.Assertions.*;
/**
- * {@link DeviceStateUpdateTriggerMatcher} 的单元测试
+ * {@link IotDeviceStateUpdateTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
-@SpringJUnitConfig(DeviceStateUpdateTriggerMatcherTest.TestConfig.class)
-public class DeviceStateUpdateTriggerMatcherTest {
+public class IotDeviceStateUpdateTriggerMatcherTest extends IotBaseConditionMatcherTest {
- @Configuration
- static class TestConfig {
- @Bean
- public SpringUtil springUtil() {
- return new SpringUtil();
- }
- }
-
- private DeviceStateUpdateTriggerMatcher matcher;
+ private IotDeviceStateUpdateTriggerMatcher matcher;
@BeforeEach
public void setUp() {
- matcher = new DeviceStateUpdateTriggerMatcher();
+ matcher = new IotDeviceStateUpdateTriggerMatcher();
}
@Test
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcherTest.java
similarity index 96%
rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java
rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcherTest.java
index f332f3097b..df47e6c28f 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcherTest.java
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
-import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
+import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -12,17 +12,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
import static org.junit.jupiter.api.Assertions.*;
/**
- * {@link TimerTriggerMatcher} 的单元测试
+ * {@link IotTimerTriggerMatcher} 的单元测试
*
* @author HUIHUI
*/
-public class TimerTriggerMatcherTest extends BaseMatcherTest {
+public class IotTimerTriggerMatcherTest extends IotBaseConditionMatcherTest {
- private TimerTriggerMatcher matcher;
+ private IotTimerTriggerMatcher matcher;
@BeforeEach
public void setUp() {
- matcher = new TimerTriggerMatcher();
+ matcher = new IotTimerTriggerMatcher();
}
@Test
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandlerTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandlerTest.java
new file mode 100644
index 0000000000..3e38e93616
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandlerTest.java
@@ -0,0 +1,126 @@
+package cn.iocoder.yudao.module.iot.service.rule.scene.timer;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
+import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
+import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager;
+import cn.iocoder.yudao.module.iot.job.rule.IotSceneRuleJob;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.quartz.SchedulerException;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link IotSceneRuleTimerHandler} 的单元测试类
+ *
+ * @author HUIHUI
+ */
+@ExtendWith(MockitoExtension.class)
+public class IotSceneRuleTimerHandlerTest {
+
+ @Mock
+ private IotSchedulerManager schedulerManager;
+
+ @InjectMocks
+ private IotSceneRuleTimerHandler timerHandler;
+
+ @BeforeEach
+ void setUp() {
+ // 重置 Mock 对象
+ reset(schedulerManager);
+ }
+
+ @Test
+ public void testRegisterTimerTriggers_success() throws SchedulerException {
+ // 准备参数
+ Long sceneRuleId = 1L;
+ IotSceneRuleDO sceneRule = new IotSceneRuleDO();
+ sceneRule.setId(sceneRuleId);
+ sceneRule.setStatus(0); // 0 表示启用
+ // 创建定时触发器
+ IotSceneRuleDO.Trigger timerTrigger = new IotSceneRuleDO.Trigger();
+ timerTrigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
+ timerTrigger.setCronExpression("0 0 12 * * ?"); // 每天中午12点
+ sceneRule.setTriggers(ListUtil.toList(timerTrigger));
+
+ // 调用
+ timerHandler.registerTimerTriggers(sceneRule);
+
+ // 验证
+ verify(schedulerManager, times(1)).addOrUpdateJob(
+ eq(IotSceneRuleJob.class),
+ eq("iot_scene_rule_timer_" + sceneRuleId),
+ eq("0 0 12 * * ?"),
+ eq(IotSceneRuleJob.buildJobDataMap(sceneRuleId))
+ );
+ }
+
+ @Test
+ public void testRegisterTimerTriggers_noTimerTrigger() throws SchedulerException {
+ // 准备参数 - 没有定时触发器
+ IotSceneRuleDO sceneRule = new IotSceneRuleDO();
+ sceneRule.setStatus(0); // 0 表示启用
+ // 创建非定时触发器
+ IotSceneRuleDO.Trigger deviceTrigger = new IotSceneRuleDO.Trigger();
+ deviceTrigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType());
+ sceneRule.setTriggers(ListUtil.toList(deviceTrigger));
+
+ // 调用
+ timerHandler.registerTimerTriggers(sceneRule);
+
+ // 验证 - 不应该调用调度器
+ verify(schedulerManager, never()).addOrUpdateJob(any(), any(), any(), any());
+ }
+
+ @Test
+ public void testRegisterTimerTriggers_emptyCronExpression() throws SchedulerException {
+ // 准备参数 - CRON 表达式为空
+ Long sceneRuleId = 2L;
+ IotSceneRuleDO sceneRule = new IotSceneRuleDO();
+ sceneRule.setId(sceneRuleId);
+ sceneRule.setStatus(0); // 0 表示启用
+ // 创建定时触发器但没有 CRON 表达式
+ IotSceneRuleDO.Trigger timerTrigger = new IotSceneRuleDO.Trigger();
+ timerTrigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
+ timerTrigger.setCronExpression(""); // 空的 CRON 表达式
+ sceneRule.setTriggers(ListUtil.toList(timerTrigger));
+
+ // 调用
+ timerHandler.registerTimerTriggers(sceneRule);
+
+ // 验证 - 不应该调用调度器
+ verify(schedulerManager, never()).addOrUpdateJob(any(), any(), any(), any());
+ }
+
+ @Test
+ public void testUnregisterTimerTriggers_success() throws SchedulerException {
+ // 准备参数
+ Long sceneRuleId = 3L;
+
+ // 调用
+ timerHandler.unregisterTimerTriggers(sceneRuleId);
+
+ // 验证
+ verify(schedulerManager, times(1)).deleteJob("iot_scene_rule_timer_" + sceneRuleId);
+ }
+
+ @Test
+ public void testPauseTimerTriggers_success() throws SchedulerException {
+ // 准备参数
+ Long sceneRuleId = 4L;
+
+ // 调用
+ timerHandler.pauseTimerTriggers(sceneRuleId);
+
+ // 验证
+ verify(schedulerManager, times(1)).pauseJob("iot_scene_rule_timer_" + sceneRuleId);
+ }
+
+}