diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml
index 02644cb..75cdd32 100644
--- a/yudao-module-iot/yudao-module-iot-biz/pom.xml
+++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -84,6 +84,13 @@
test
+
+
+ org.eclipse.paho
+ org.eclipse.paho.client.mqttv3
+ true
+
+
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 b42e1c0..0b7b4fa 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
@@ -20,6 +20,7 @@ import lombok.Data;
@JsonSubTypes.Type(value = IotDataSinkTcpConfig.class, name = "2"),
@JsonSubTypes.Type(value = IotDataSinkWebSocketConfig.class, name = "3"),
@JsonSubTypes.Type(value = IotDataSinkMqttConfig.class, name = "10"),
+ @JsonSubTypes.Type(value = IotDataSinkDatabaseConfig.class, name = "20"),
@JsonSubTypes.Type(value = IotDataSinkRedisConfig.class, name = "21"),
@JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"),
@JsonSubTypes.Type(value = IotDataSinkRabbitMQConfig.class, name = "31"),
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkDatabaseConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkDatabaseConfig.java
new file mode 100644
index 0000000..22abb98
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkDatabaseConfig.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import lombok.Data;
+
+/**
+ * IoT Database 配置 {@link IotAbstractDataSinkConfig} 实现类
+ *
+ * 通过 JDBC 连接数据库,将设备消息写入指定表。
+ * 支持 MySQL、PostgreSQL、Oracle、SQL Server、DM 达梦等数据库,
+ * HikariCP 会根据 JDBC URL 自动加载对应的驱动。
+ *
+ * @author HUIHUI
+ */
+@Data
+public class IotDataSinkDatabaseConfig extends IotAbstractDataSinkConfig {
+
+ /**
+ * JDBC 连接地址
+ *
+ * 例如:jdbc:mysql://localhost:3306/iot_data
+ * 例如:jdbc:postgresql://localhost:5432/iot_data
+ * 例如:jdbc:dm://localhost:5236/iot_data
+ *
+ * HikariCP 会根据 URL 自动检测并加载对应的 JDBC 驱动
+ */
+ private String jdbcUrl;
+ /**
+ * 数据库用户名
+ */
+ private String username;
+ /**
+ * 数据库密码
+ */
+ private String password;
+ /**
+ * 目标表名
+ *
+ * 设备消息将以固定结构写入该表
+ */
+ private String tableName;
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/ThingModelEvent.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/ThingModelEvent.java
index 4d85370..913ae28 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/ThingModelEvent.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/ThingModelEvent.java
@@ -9,7 +9,6 @@ import lombok.Data;
import java.util.List;
-// TODO @puhui999:感觉这个,是不是放到 dal 里会好点?(讨论下,先不改哈)
/**
* IoT 物模型中的事件
*
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java
index 96b477d..c8f8aa0 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java
@@ -19,9 +19,9 @@ public enum IotDataSinkTypeEnum implements ArrayValuable {
TCP(2, "TCP"),
WEBSOCKET(3, "WebSocket"),
- MQTT(10, "MQTT"), // TODO @puhui999:待实现;
+ MQTT(10, "MQTT"),
- DATABASE(20, "Database"), // TODO @puhui999:待实现;
+ DATABASE(20, "Database"),
REDIS(21, "Redis"),
ROCKETMQ(30, "RocketMQ"),
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionOperatorEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionOperatorEnum.java
index 9bf90cf..ae4997e 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionOperatorEnum.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionOperatorEnum.java
@@ -36,13 +36,11 @@ public enum IotSceneRuleConditionOperatorEnum implements ArrayValuable {
// ========== 特殊:不放在字典里 ==========
- // TODO @puhui999:@芋艿:需要测试下
DATE_TIME_GREATER_THAN("date_time_>", "#source > #value"), // 在时间之后:时间戳
DATE_TIME_LESS_THAN("date_time_<", "#source < #value"), // 在时间之前:时间戳
DATE_TIME_BETWEEN("date_time_between", // 在时间之间:时间戳
"(#source >= #values.get(0)) && (#source <= #values.get(1))"),
- // TODO @puhui999:@芋艿:需要测试下
TIME_GREATER_THAN("time_>", "#source.isAfter(#value)"), // 在当日时间之后:HH:mm:ss
TIME_LESS_THAN("time_<", "#source.isBefore(#value)"), // 在当日时间之前:HH:mm:ss
TIME_BETWEEN("time_between", // 在当日时间之间:HH:mm:ss
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleCacheableAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleCacheableAction.java
index cc282e1..4739743 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleCacheableAction.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleCacheableAction.java
@@ -12,9 +12,6 @@ import lombok.extern.slf4j.Slf4j;
import java.time.Duration;
-// TODO @芋艿:数据库
-// TODO @芋艿:mqtt
-
/**
* 可缓存的 {@link IotDataRuleAction} 抽象实现
*
@@ -78,6 +75,18 @@ public abstract class IotDataRuleCacheableAction implements Io
}
/**
+ * 使指定配置的 Producer 缓存失效
+ *
+ * 当子类检测到 Producer 不可用时(如连接断开),可调用此方法踢出缓存,
+ * 下次 {@link #getProducer(Object)} 调用将触发 {@link #initProducer(Object)} 重新创建。
+ *
+ * @param config 配置信息
+ */
+ protected void invalidateProducer(Config config) {
+ PRODUCER_CACHE.invalidate(config);
+ }
+
+ /**
* 初始化生产者
*
* @param config 配置信息
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDatabaseDataRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDatabaseDataRuleAction.java
new file mode 100644
index 0000000..8168df4
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDatabaseDataRuleAction.java
@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.iot.service.rule.data.action;
+
+import cn.hutool.core.util.StrUtil;
+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.IotDataSinkDatabaseConfig;
+import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+/**
+ * Database 的 {@link IotDataRuleAction} 实现类
+ *
+ * 通过 JDBC 连接数据库,将设备消息写入指定表。
+ * 支持 MySQL、PostgreSQL、Oracle、SQL Server、DM 达梦等数据库,
+ * HikariCP 会根据 JDBC URL 自动加载对应的驱动。
+ *
+ * @author HUIHUI
+ */
+@Component
+@Slf4j
+public class IotDatabaseDataRuleAction extends
+ IotDataRuleCacheableAction {
+
+ @Override
+ public Integer getType() {
+ return IotDataSinkTypeEnum.DATABASE.getType();
+ }
+
+ @Override
+ public void execute(IotDeviceMessage message, IotDataSinkDatabaseConfig config) throws Exception {
+ try {
+ // 1. 获取或创建 JdbcTemplate
+ JdbcTemplate jdbcTemplate = getProducer(config);
+
+ // 2. 构建并执行 INSERT SQL
+ String sql = StrUtil.format(
+ "INSERT INTO {} (id, device_id, tenant_id, method, report_time, data, create_time) VALUES (?, ?, ?, ?, ?, ?, NOW())",
+ config.getTableName());
+ String messageJson = JsonUtils.toJsonString(message);
+ jdbcTemplate.update(sql,
+ message.getId(),
+ message.getDeviceId(),
+ message.getTenantId(),
+ message.getMethod(),
+ message.getReportTime(),
+ messageJson);
+ log.info("[execute][message({}) config({}) 写入数据库成功,table: {}]",
+ message.getId(), config, config.getTableName());
+ } catch (Exception e) {
+ log.error("[execute][message({}) config({}) 写入数据库失败]", message, config, e);
+ throw e;
+ }
+ }
+
+ @Override
+ protected JdbcTemplate initProducer(IotDataSinkDatabaseConfig config) throws Exception {
+ // 使用 HikariCP 连接池,HikariCP 会根据 JDBC URL 自动检测并加载对应的数据库驱动
+ HikariConfig hikariConfig = new HikariConfig();
+ hikariConfig.setJdbcUrl(config.getJdbcUrl());
+ hikariConfig.setUsername(config.getUsername());
+ hikariConfig.setPassword(config.getPassword());
+ // 连接池配置
+ hikariConfig.setMaximumPoolSize(5); // 数据流转场景,不需要太多连接
+ hikariConfig.setMinimumIdle(1);
+ hikariConfig.setConnectionTimeout(10000); // 连接超时 10 秒
+ hikariConfig.setIdleTimeout(300000); // 空闲超时 5 分钟
+ hikariConfig.setMaxLifetime(600000); // 最大生命周期 10 分钟
+ HikariDataSource dataSource = new HikariDataSource(hikariConfig);
+ log.info("[initProducer][数据库连接池创建成功,jdbcUrl: {}]", config.getJdbcUrl());
+ return new JdbcTemplate(dataSource);
+ }
+
+ @Override
+ protected void closeProducer(JdbcTemplate producer) throws Exception {
+ if (producer.getDataSource() instanceof HikariDataSource) {
+ ((HikariDataSource) producer.getDataSource()).close();
+ }
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotMqttDataRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotMqttDataRuleAction.java
new file mode 100644
index 0000000..2e5d896
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotMqttDataRuleAction.java
@@ -0,0 +1,108 @@
+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.IotDataSinkMqttConfig;
+import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * MQTT 的 {@link IotDataRuleAction} 实现类
+ *
+ * @author HUIHUI
+ */
+@ConditionalOnClass(name = "org.eclipse.paho.client.mqttv3.MqttClient")
+@Component
+@Slf4j
+public class IotMqttDataRuleAction extends
+ IotDataRuleCacheableAction {
+
+ /**
+ * 默认 QoS 等级(至少一次)
+ */
+ private static final int DEFAULT_QOS = 1;
+
+ @Override
+ public Integer getType() {
+ return IotDataSinkTypeEnum.MQTT.getType();
+ }
+
+ @Override
+ public void execute(IotDeviceMessage message, IotDataSinkMqttConfig config) throws Exception {
+ try {
+ // 1. 获取或创建 MqttClient
+ MqttClient mqttClient = getProducer(config);
+
+ // 2.1 检查连接状态,如果断开则踢出缓存并重新创建
+ if (!mqttClient.isConnected()) {
+ log.warn("[execute][MQTT 连接已断开,重新创建客户端,服务器: {}]", config.getUrl());
+ invalidateProducer(config); // 踢出旧的断连客户端,触发 closeProducer
+ mqttClient = getProducer(config); // 触发 initProducer 创建全新连接
+ }
+
+ // 2.2 构建并发送消息
+ MqttMessage mqttMessage = new MqttMessage(JsonUtils.toJsonString(message).getBytes(StandardCharsets.UTF_8));
+ mqttMessage.setQos(DEFAULT_QOS);
+ mqttClient.publish(config.getTopic(), mqttMessage);
+ log.info("[execute][message({}) 发送成功,MQTT 服务器: {},topic: {}]",
+ message.getId(), config.getUrl(), config.getTopic());
+ } catch (Exception e) {
+ log.error("[execute][message({}) 发送失败,MQTT 服务器: {}]",
+ message.getId(), config.getUrl(), e);
+ throw e;
+ }
+ }
+
+ @Override
+ protected MqttClient initProducer(IotDataSinkMqttConfig config) throws Exception {
+ // 1. 创建 MqttClient,使用内存持久化
+ // 拼接时间戳后缀,避免多个规则指向同一 Broker 时 clientId 冲突
+ String clientId = config.getClientId() + "_" + System.currentTimeMillis();
+ MqttClient mqttClient = new MqttClient(config.getUrl(), clientId, new MemoryPersistence());
+
+ // 2. 连接到 MQTT Broker
+ mqttClient.connect(buildConnectOptions(config));
+ log.info("[initProducer][MQTT 客户端创建并连接成功,服务器: {},clientId: {}]",
+ config.getUrl(), clientId);
+ return mqttClient;
+ }
+
+ @Override
+ protected void closeProducer(MqttClient producer) throws Exception {
+ if (producer.isConnected()) {
+ producer.disconnect();
+ }
+ producer.close();
+ }
+
+ /**
+ * 构建 MQTT 连接选项
+ *
+ * @param config MQTT 配置
+ * @return 连接选项
+ */
+ private MqttConnectOptions buildConnectOptions(IotDataSinkMqttConfig config) {
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setCleanSession(true);
+ options.setConnectionTimeout(10); // 连接超时 10 秒
+ options.setKeepAliveInterval(20); // 心跳间隔 20 秒
+ // 注意:不开启 automaticReconnect,由 execute() 中的 isConnected() 手动控制重连,避免竞争
+ // 设置认证信息(如果有)
+ if (config.getUsername() != null) {
+ options.setUserName(config.getUsername());
+ }
+ if (config.getPassword() != null) {
+ options.setPassword(config.getPassword().toCharArray());
+ }
+ return options;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotWebSocketDataRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotWebSocketDataRuleAction.java
index 7471642..31bb96a 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotWebSocketDataRuleAction.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotWebSocketDataRuleAction.java
@@ -97,7 +97,8 @@ public class IotWebSocketDataRuleAction extends
}
}
- // TODO @puhui999:为什么这里要加锁呀?
+ // 加锁原因:多线程并发发送消息时,多个线程可能同时检测到连接断开并尝试重连,
+ // 使用锁 + 双重检查保证同一服务器地址只有一个线程执行重连操作
/**
* 使用锁进行重连,保证同一服务器地址的重连操作线程安全
*
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/websocket/IotWebSocketClient.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/websocket/IotWebSocketClient.java
index 8eba723..0eacb42 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/websocket/IotWebSocketClient.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/websocket/IotWebSocketClient.java
@@ -33,6 +33,13 @@ public class IotWebSocketClient {
private volatile WebSocket webSocket;
private final AtomicBoolean connected = new AtomicBoolean(false);
+ /**
+ * WebSocket 正常关闭状态码
+ *
+ * @see RFC 6455 - 定义的状态码
+ */
+ private static final int NORMAL_CLOSURE_STATUS = 1000;
+
public IotWebSocketClient(String serverUrl, Integer connectTimeoutMs, Integer sendTimeoutMs, String dataFormat) {
this.serverUrl = serverUrl;
this.connectTimeoutMs = connectTimeoutMs != null ? connectTimeoutMs : IotDataSinkWebSocketConfig.DEFAULT_CONNECT_TIMEOUT_MS;
@@ -123,9 +130,8 @@ public class IotWebSocketClient {
public void close() {
try {
if (webSocket != null) {
- // 发送正常关闭帧,状态码 1000 表示正常关闭
- // TODO @puhui999:有没 1000 的枚举哈?在 okhttp 里
- webSocket.close(1000, "客户端主动关闭");
+ // 发送正常关闭帧
+ webSocket.close(NORMAL_CLOSURE_STATUS, "客户端主动关闭");
webSocket = null;
}
if (okHttpClient != null) {
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimeHelper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimeHelper.java
index df1ac23..01ddd57 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimeHelper.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimeHelper.java
@@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition.IotCurrentTimeConditionMatcher;
import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotTimerConditionEvaluator;
+import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
@@ -22,6 +23,7 @@ import java.util.List;
* @author HUIHUI
*/
@Slf4j
+@UtilityClass
public class IotSceneRuleTimeHelper {
/**
@@ -34,11 +36,6 @@ public class IotSceneRuleTimeHelper {
*/
private static final DateTimeFormatter TIME_FORMATTER_SHORT = DateTimeFormatter.ofPattern("HH:mm");
- // TODO @puhui999:可以使用 lombok 简化
- private IotSceneRuleTimeHelper() {
- // 工具类,禁止实例化
- }
-
/**
* 判断是否为日期时间操作符
*
@@ -136,7 +133,6 @@ public class IotSceneRuleTimeHelper {
}
long startTimestamp = Long.parseLong(timestampRange.get(0).trim());
long endTimestamp = Long.parseLong(timestampRange.get(1).trim());
- // TODO @puhui999:hutool 里,看看有没 between 方法
return currentTimestamp >= startTimestamp && currentTimestamp <= endTimestamp;
}
@@ -188,7 +184,6 @@ public class IotSceneRuleTimeHelper {
}
LocalTime startTime = parseTime(timeRange.get(0).trim());
LocalTime endTime = parseTime(timeRange.get(1).trim());
- // TODO @puhui999:hutool 里,看看有没 between 方法
return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime);
}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java
index 642fb5e..842d081 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
+import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -73,8 +74,7 @@ public class IotDeviceServiceInvokeTriggerMatcher implements IotSceneRuleTrigger
private boolean matchParameterCondition(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
// 1.1 从消息中提取服务调用的输入参数
Map inputParams = IotDeviceMessageUtils.extractServiceInputParams(message);
- // TODO @puhui999:要考虑 empty 的情况么?
- if (inputParams == null) {
+ if (CollUtil.isEmpty(inputParams)) {
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中缺少服务输入参数");
return false;
}
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
index 73e1867..cbbbd7b 100644
--- 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
@@ -9,9 +9,6 @@ import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.HashMap;
-import java.util.Map;
-
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
@@ -45,9 +42,8 @@ class IotTcpDataRuleActionTest {
assertEquals(expectedType, actualType);
}
- // TODO @puhui999:_ 后面是小写哈,单测的命名规则。
@Test
- public void testInitProducer_Success() throws Exception {
+ public void testInitProducer_success() throws Exception {
// 准备参数
IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
config.setHost("localhost");
@@ -63,7 +59,7 @@ class IotTcpDataRuleActionTest {
}
@Test
- public void testInitProducer_InvalidHost() {
+ public void testInitProducer_invalidHost() {
// 准备参数
IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
config.setHost("");
@@ -81,7 +77,7 @@ class IotTcpDataRuleActionTest {
}
@Test
- public void testInitProducer_InvalidPort() {
+ public void testInitProducer_invalidPort() {
// 准备参数
IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
config.setHost("localhost");
@@ -108,7 +104,7 @@ class IotTcpDataRuleActionTest {
}
@Test
- public void testExecute_WithValidConfig() {
+ public void testExecute_withValidConfig() {
// 准备参数
IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.report",
"{\"temperature\": 25.5, \"humidity\": 60}");
@@ -128,7 +124,7 @@ class IotTcpDataRuleActionTest {
}
@Test
- public void testConfig_DefaultValues() {
+ public void testConfig_defaultValues() {
// 准备参数
IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
@@ -146,12 +142,10 @@ class IotTcpDataRuleActionTest {
@Test
public void testMessageSerialization() {
// 准备参数
- Map params = new HashMap<>();
- params.put("temperature", 25.5);
IotDeviceMessage message = IotDeviceMessage.builder()
.deviceId(123L)
.method("thing.property.report")
- .params(params)
+ .params("{\"temperature\": 25.5}")
.build();
// 调用方法
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceSimpleTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceSimpleTest.java
index f6e7c5f..c337483 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceSimpleTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceSimpleTest.java
@@ -5,11 +5,12 @@ import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
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.framework.job.core.IotSchedulerManager;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotSceneRuleAction;
+import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager;
import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotSceneRuleTimerHandler;
+import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotTimerConditionEvaluator;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
@@ -42,19 +43,22 @@ public class IotSceneRuleServiceSimpleTest extends BaseMockitoUnitTest {
private List sceneRuleActions;
@Mock
- private IotSchedulerManager schedulerManager;
+ private IotSceneRuleTimerHandler timerHandler;
@Mock
- private IotProductService productService;
+ private IotTimerConditionEvaluator timerConditionEvaluator;
@Mock
- private IotDeviceService deviceService;
+ private IotSceneRuleMatcherManager sceneRuleMatcherManager;
@Mock
- private IotSceneRuleTimerHandler timerHandler;
+ private IotProductService productService;
+
+ @Mock
+ private IotDeviceService deviceService;
@Test
- public void testCreateScene_Rule_success() {
+ public void testCreateScene_rule_success() {
// 准备参数
IotSceneRuleSaveReqVO createReqVO = randomPojo(IotSceneRuleSaveReqVO.class, o -> {
o.setId(null);
@@ -80,7 +84,7 @@ public class IotSceneRuleServiceSimpleTest extends BaseMockitoUnitTest {
}
@Test
- public void testUpdateScene_Rule_success() {
+ public void testUpdateScene_rule_success() {
// 准备参数
Long id = randomLongId();
IotSceneRuleSaveReqVO updateReqVO = randomPojo(IotSceneRuleSaveReqVO.class, o -> {
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimerConditionIntegrationTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimerConditionIntegrationTest.java
index bf17f11..2a68a0d 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimerConditionIntegrationTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimerConditionIntegrationTest.java
@@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.iot.service.rule.scene;
import cn.hutool.core.collection.ListUtil;
-import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
@@ -15,12 +14,13 @@ 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.device.property.IotDevicePropertyService;
import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotSceneRuleAction;
+import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager;
import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotSceneRuleTimerHandler;
import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotTimerConditionEvaluator;
+import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import org.junit.jupiter.api.*;
import org.mockito.InjectMocks;
import org.mockito.Mock;
-import org.springframework.context.support.GenericApplicationContext;
import java.lang.reflect.Field;
import java.util.*;
@@ -63,6 +63,12 @@ public class IotSceneRuleTimerConditionIntegrationTest extends BaseMockitoUnitTe
@Mock
private IotSceneRuleTimerHandler timerHandler;
+ @Mock
+ private IotSceneRuleMatcherManager sceneRuleMatcherManager;
+
+ @Mock
+ private IotProductService productService;
+
private IotTimerConditionEvaluator timerConditionEvaluator;
// 测试常量
@@ -71,12 +77,6 @@ public class IotSceneRuleTimerConditionIntegrationTest extends BaseMockitoUnitTe
private static final Long DEVICE_ID = 100L;
private static final String PROPERTY_IDENTIFIER = "temperature";
- @BeforeAll
- static void initSpringContext() {
- // SpringExpressionUtils 依赖 SpringUtil 持有的 ApplicationContext
- new SpringUtil().setApplicationContext(new GenericApplicationContext());
- }
-
@BeforeEach
void setUp() {
// 创建并注入 timerConditionEvaluator 的依赖
@@ -144,15 +144,14 @@ public class IotSceneRuleTimerConditionIntegrationTest extends BaseMockitoUnitTe
IotDevicePropertyDO property = new IotDevicePropertyDO();
property.setValue(value);
properties.put(identifier, property);
- // 多条件组 OR 短路时部分 stub 用不上,使用 lenient 容忍未消费的 stub
- lenient().when(devicePropertyService.getLatestDeviceProperties(deviceId)).thenReturn(properties);
+ when(devicePropertyService.getLatestDeviceProperties(deviceId)).thenReturn(properties);
}
private void mockDeviceState(Long deviceId, Integer state) {
IotDeviceDO device = new IotDeviceDO();
device.setId(deviceId);
device.setState(state);
- lenient().when(deviceService.getDevice(deviceId)).thenReturn(device);
+ when(deviceService.getDevice(deviceId)).thenReturn(device);
}
/**
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcherTest.java
index 586d948..1bc7c11 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcherTest.java
@@ -59,7 +59,7 @@ public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherT
// ========== 时间戳条件测试 ==========
@Test
- public void testMatches_DateTimeGreaterThan_success() {
+ public void testMatches_dateTimeGreaterThan_success() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
long pastTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8"));
@@ -76,7 +76,7 @@ public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherT
}
@Test
- public void testMatches_DateTimeGreaterThan_fail() {
+ public void testMatches_dateTimeGreaterThan_fail() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8"));
@@ -93,7 +93,7 @@ public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherT
}
@Test
- public void testMatches_DateTimeLessThan_success() {
+ public void testMatches_dateTimeLessThan_success() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8"));
@@ -110,7 +110,7 @@ public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherT
}
@Test
- public void testMatches_DateTimeBetween_success() {
+ public void testMatches_dateTimeBetween_success() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
long startTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8"));
@@ -128,7 +128,7 @@ public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherT
}
@Test
- public void testMatches_DateTimeBetween_fail() {
+ public void testMatches_dateTimeBetween_fail() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
long startTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8"));
@@ -148,7 +148,7 @@ public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherT
// ========== 当日时间条件测试 ==========
@Test
- public void testMatches_TimeGreaterThan_earlyMorning() {
+ public void testMatches_timeGreaterThan_earlyMorning() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
@@ -165,7 +165,7 @@ public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherT
}
@Test
- public void testMatches_TimeLessThan_lateNight() {
+ public void testMatches_timeLessThan_lateNight() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
@@ -182,7 +182,7 @@ public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherT
}
@Test
- public void testMatches_TimeBetween_allDay() {
+ public void testMatches_timeBetween_allDay() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
@@ -198,7 +198,7 @@ public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherT
}
@Test
- public void testMatches_TimeBetween_workingHours() {
+ public void testMatches_timeBetween_workingHours() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java
index 500a739..07041fc 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java
@@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
-import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
@@ -10,9 +9,6 @@ import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMa
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import java.util.HashMap;
-import java.util.Map;
-
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.*;
@@ -346,10 +342,7 @@ public class IotDeviceStateConditionMatcherTest extends IotBaseConditionMatcherT
private IotDeviceMessage createDeviceMessage(Integer deviceState) {
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
- message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod());
- Map params = new HashMap<>();
- params.put("state", deviceState);
- message.setParams(params);
+ message.setParams(deviceState);
return message;
}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcherTest.java
index bd1cba4..9cf5142 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcherTest.java
@@ -1,12 +1,9 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
-import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
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.core.topic.event.IotDeviceEventPostReqDTO;
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;
@@ -357,184 +354,6 @@ public class IotDeviceEventPostTriggerMatcherTest extends IotBaseConditionMatche
assertFalse(result);
}
- @Test
- public void testMatches_scalarValueEqualsSuccess() {
- // 标量事件值 + operator='=' + value 命中:必须匹配;防止"把整个 params Map 当作源值"的回归
- String eventIdentifier = randomString();
- Map eventParams = MapUtil.builder(new HashMap())
- .put("identifier", eventIdentifier)
- .put("value", "normal")
- .build();
- IotDeviceMessage message = createEventPostMessage(eventParams);
- IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
- trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator());
- trigger.setValue("normal");
-
- boolean result = matcher.matches(message, trigger);
-
- assertTrue(result);
- }
-
- @Test
- public void testMatches_scalarValueEqualsMismatch() {
- // 标量事件值不等于配置值:应不匹配
- String eventIdentifier = randomString();
- Map eventParams = MapUtil.builder(new HashMap())
- .put("identifier", eventIdentifier)
- .put("value", "abnormal")
- .build();
- IotDeviceMessage message = createEventPostMessage(eventParams);
- IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
- trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator());
- trigger.setValue("normal");
-
- boolean result = matcher.matches(message, trigger);
-
- assertFalse(result);
- }
-
- @Test
- public void testMatches_pojoParamsScalarValueSuccess() {
- // 本地总线场景:params 是 IotDeviceEventPostReqDTO POJO 而非 Map,仍应能正确匹配
- String eventIdentifier = randomString();
- IotDeviceMessage message = new IotDeviceMessage();
- message.setDeviceId(randomLongId());
- message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod());
- message.setParams(IotDeviceEventPostReqDTO.of(eventIdentifier, "normal"));
- IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
- trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator());
- trigger.setValue("normal");
-
- boolean result = matcher.matches(message, trigger);
-
- assertTrue(result);
- }
-
- @Test
- public void testMatches_structValueEqualsSuccess() {
- // 结构体事件值 + 比较值是 JSON 对象字面量:JSON 反序列化后整体相等
- String eventIdentifier = randomString();
- Map eventValue = MapUtil.builder(new HashMap())
- .put("level", "high")
- .put("message", "over temperature")
- .build();
- Map eventParams = MapUtil.builder(new HashMap())
- .put("identifier", eventIdentifier)
- .put("value", eventValue)
- .build();
- IotDeviceMessage message = createEventPostMessage(eventParams);
- IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
- trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator());
- trigger.setValue("{\"level\":\"high\",\"message\":\"over temperature\"}");
-
- boolean result = matcher.matches(message, trigger);
-
- assertTrue(result);
- }
-
- @Test
- public void testMatches_structValueEqualsKeyOrderInsensitive() {
- // 比较值的 JSON 字段顺序与事件 value 不同,应仍然匹配(HashMap.equals 与顺序无关)
- String eventIdentifier = randomString();
- Map eventValue = MapUtil.builder(new HashMap())
- .put("level", "high")
- .put("code", 500)
- .build();
- Map eventParams = MapUtil.builder(new HashMap())
- .put("identifier", eventIdentifier)
- .put("value", eventValue)
- .build();
- IotDeviceMessage message = createEventPostMessage(eventParams);
- IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
- trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator());
- trigger.setValue("{\"code\":500,\"level\":\"high\"}");
-
- boolean result = matcher.matches(message, trigger);
-
- assertTrue(result);
- }
-
- @Test
- public void testMatches_structValueEqualsMismatch() {
- // 结构体值字段不一致:应不匹配
- String eventIdentifier = randomString();
- Map eventValue = MapUtil.builder(new HashMap())
- .put("level", "low")
- .build();
- Map eventParams = MapUtil.builder(new HashMap())
- .put("identifier", eventIdentifier)
- .put("value", eventValue)
- .build();
- IotDeviceMessage message = createEventPostMessage(eventParams);
- IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
- trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator());
- trigger.setValue("{\"level\":\"high\"}");
-
- boolean result = matcher.matches(message, trigger);
-
- assertFalse(result);
- }
-
- @Test
- public void testMatches_structValueNotEqualsSuccess() {
- // 结构体值 + != 操作符:内容不一致时应匹配
- String eventIdentifier = randomString();
- Map eventValue = MapUtil.builder(new HashMap())
- .put("level", "low")
- .build();
- Map eventParams = MapUtil.builder(new HashMap())
- .put("identifier", eventIdentifier)
- .put("value", eventValue)
- .build();
- IotDeviceMessage message = createEventPostMessage(eventParams);
- IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
- trigger.setOperator(IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator());
- trigger.setValue("{\"level\":\"high\"}");
-
- boolean result = matcher.matches(message, trigger);
-
- assertTrue(result);
- }
-
- @Test
- public void testMatches_arrayValueEqualsSuccess() {
- // 数组事件值:JSON 解析后按序相等
- String eventIdentifier = randomString();
- Map eventParams = MapUtil.builder(new HashMap())
- .put("identifier", eventIdentifier)
- .put("value", ListUtil.of("a", "b", "c"))
- .build();
- IotDeviceMessage message = createEventPostMessage(eventParams);
- IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
- trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator());
- trigger.setValue("[\"a\",\"b\",\"c\"]");
-
- boolean result = matcher.matches(message, trigger);
-
- assertTrue(result);
- }
-
- @Test
- public void testMatches_structValueInvalidJsonComparator() {
- // 比较值不是合法 JSON:结构体场景下视为不匹配,不抛异常
- String eventIdentifier = randomString();
- Map eventValue = MapUtil.builder(new HashMap())
- .put("level", "high")
- .build();
- Map eventParams = MapUtil.builder(new HashMap())
- .put("identifier", eventIdentifier)
- .put("value", eventValue)
- .build();
- IotDeviceMessage message = createEventPostMessage(eventParams);
- IotSceneRuleDO.Trigger trigger = createValidTrigger(eventIdentifier);
- trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator());
- trigger.setValue("not a json");
-
- boolean result = matcher.matches(message, trigger);
-
- assertFalse(result);
- }
-
// ========== 辅助方法 ==========
/**
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcherTest.java
index c617f42..a6b2b0a 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcherTest.java
@@ -4,7 +4,6 @@ import cn.hutool.core.map.MapUtil;
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.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;
@@ -486,7 +485,7 @@ public class IotDeviceServiceInvokeTriggerMatcherTest extends IotBaseConditionMa
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
trigger.setIdentifier(serviceIdentifier);
- trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator());
+ trigger.setOperator("=="); // 等于操作符
trigger.setValue("auto");
// 调用
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java
index 5d6bf88..ad40c9f 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java
@@ -10,9 +10,6 @@ import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMa
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import java.util.HashMap;
-import java.util.Map;
-
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static org.junit.jupiter.api.Assertions.*;
@@ -251,9 +248,7 @@ public class IotDeviceStateUpdateTriggerMatcherTest extends IotBaseConditionMatc
IotDeviceMessage message = new IotDeviceMessage();
message.setDeviceId(randomLongId());
message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod());
- Map params = new HashMap<>();
- params.put("state", state);
- message.setParams(params);
+ message.setParams(state);
return message;
}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcherTest.java
index d7585d6..df47e6c 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcherTest.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcherTest.java
@@ -247,7 +247,7 @@ public class IotTimerTriggerMatcherTest extends IotBaseConditionMatcherTest {
public void testMatches_rangeCronSuccess() {
// 准备参数
IotDeviceMessage message = createDeviceMessage();
- IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9-17 ? * MON-FRI"); // 工作日 9-17 点
+ IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9-17 * * MON-FRI"); // 工作日9-17点
// 调用
boolean result = matcher.matches(message, trigger);