Commit c1657149a85d8c5719ec444da6ff3529c9077e3b

Authored by 芋道源码
Committed by Gitee
2 parents e639dbfe c5b4968e

!1434 完善 code review 提到的一些问题

Merge pull request !1434 from puhui999/feature/iot
Showing 26 changed files with 793 additions and 74 deletions
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; @@ -17,6 +17,8 @@ import lombok.Data;
17 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true) 17 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
18 @JsonSubTypes({ 18 @JsonSubTypes({
19 @JsonSubTypes.Type(value = IotDataSinkHttpConfig.class, name = "1"), 19 @JsonSubTypes.Type(value = IotDataSinkHttpConfig.class, name = "1"),
  20 + @JsonSubTypes.Type(value = IotDataSinkTcpConfig.class, name = "2"),
  21 + @JsonSubTypes.Type(value = IotDataSinkWebSocketConfig.class, name = "3"),
20 @JsonSubTypes.Type(value = IotDataSinkMqttConfig.class, name = "10"), 22 @JsonSubTypes.Type(value = IotDataSinkMqttConfig.class, name = "10"),
21 @JsonSubTypes.Type(value = IotDataSinkRedisConfig.class, name = "21"), 23 @JsonSubTypes.Type(value = IotDataSinkRedisConfig.class, name = "21"),
22 @JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"), 24 @JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"),
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkTcpConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * IoT TCP 配置 {@link IotAbstractDataSinkConfig} 实现类
  7 + *
  8 + * @author HUIHUI
  9 + */
  10 +@Data
  11 +public class IotDataSinkTcpConfig extends IotAbstractDataSinkConfig {
  12 +
  13 + /**
  14 + * TCP 服务器地址
  15 + */
  16 + private String host;
  17 +
  18 + /**
  19 + * TCP 服务器端口
  20 + */
  21 + private Integer port;
  22 +
  23 + /**
  24 + * 连接超时时间(毫秒)
  25 + */
  26 + private Integer connectTimeoutMs = 5000;
  27 +
  28 + /**
  29 + * 读取超时时间(毫秒)
  30 + */
  31 + private Integer readTimeoutMs = 10000;
  32 +
  33 + /**
  34 + * 是否启用 SSL
  35 + */
  36 + private Boolean ssl = false;
  37 +
  38 + /**
  39 + * SSL 证书路径(当 ssl=true 时需要)
  40 + */
  41 + private String sslCertPath;
  42 +
  43 + /**
  44 + * 数据格式:JSON 或 BINARY
  45 + */
  46 + private String dataFormat = "JSON";
  47 +
  48 + /**
  49 + * 心跳间隔时间(毫秒),0 表示不启用心跳
  50 + */
  51 + private Long heartbeatIntervalMs = 30000L;
  52 +
  53 + /**
  54 + * 重连间隔时间(毫秒)
  55 + */
  56 + private Long reconnectIntervalMs = 5000L;
  57 +
  58 + /**
  59 + * 最大重连次数
  60 + */
  61 + private Integer maxReconnectAttempts = 3;
  62 +
  63 +}
0 \ No newline at end of file 64 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkWebSocketConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * IoT WebSocket 配置 {@link IotAbstractDataSinkConfig} 实现类
  7 + * <p>
  8 + * 配置设备消息通过 WebSocket 协议发送到外部 WebSocket 服务器
  9 + * 支持 WebSocket (ws://) 和 WebSocket Secure (wss://) 连接
  10 + *
  11 + * @author HUIHUI
  12 + */
  13 +@Data
  14 +public class IotDataSinkWebSocketConfig extends IotAbstractDataSinkConfig {
  15 +
  16 + /**
  17 + * WebSocket 服务器地址
  18 + * 例如:ws://localhost:8080/ws 或 wss://example.com/ws
  19 + */
  20 + private String serverUrl;
  21 +
  22 + /**
  23 + * 连接超时时间(毫秒)
  24 + */
  25 + private Integer connectTimeoutMs = 5000;
  26 +
  27 + /**
  28 + * 发送超时时间(毫秒)
  29 + */
  30 + private Integer sendTimeoutMs = 10000;
  31 +
  32 + /**
  33 + * 心跳间隔时间(毫秒),0 表示不启用心跳
  34 + */
  35 + private Long heartbeatIntervalMs = 30000L;
  36 +
  37 + /**
  38 + * 心跳消息内容(JSON 格式)
  39 + */
  40 + private String heartbeatMessage = "{\"type\":\"heartbeat\"}";
  41 +
  42 + /**
  43 + * 子协议列表(逗号分隔)
  44 + */
  45 + private String subprotocols;
  46 +
  47 + /**
  48 + * 自定义请求头(JSON 格式)
  49 + */
  50 + private String customHeaders;
  51 +
  52 + /**
  53 + * 是否启用 SSL 证书验证(仅对 wss:// 生效)
  54 + */
  55 + private Boolean verifySslCert = true;
  56 +
  57 + /**
  58 + * 数据格式:JSON 或 TEXT
  59 + */
  60 + private String dataFormat = "JSON";
  61 +
  62 + /**
  63 + * 重连间隔时间(毫秒)
  64 + */
  65 + private Long reconnectIntervalMs = 5000L;
  66 +
  67 + /**
  68 + * 最大重连次数
  69 + */
  70 + private Integer maxReconnectAttempts = 3;
  71 +
  72 + /**
  73 + * 是否启用压缩
  74 + */
  75 + private Boolean enableCompression = false;
  76 +
  77 + /**
  78 + * 消息发送重试次数
  79 + */
  80 + private Integer sendRetryCount = 1;
  81 +
  82 + /**
  83 + * 消息发送重试间隔(毫秒)
  84 + */
  85 + private Long sendRetryIntervalMs = 1000L;
  86 +
  87 +}
0 \ No newline at end of file 88 \ No newline at end of file
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 { @@ -76,4 +76,12 @@ public interface RedisKeyConstants {
76 */ 76 */
77 String DATA_SINK = "iot:data_sink"; 77 String DATA_SINK = "iot:data_sink";
78 78
  79 + /**
  80 + * 场景联动规则的数据缓存,使用 Spring Cache 操作
  81 + * <p>
  82 + * KEY 格式:scene_rule_list_${productId}_${deviceId}
  83 + * VALUE 数据类型:String 数组(JSON),即 {@link cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO} 列表
  84 + */
  85 + String SCENE_RULE_LIST = "iot:scene_rule_list";
  86 +
79 } 87 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleAction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.data.action;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  4 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkTcpConfig;
  5 +import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
  6 +import cn.iocoder.yudao.module.iot.service.rule.data.action.tcp.IotTcpClient;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import org.springframework.stereotype.Component;
  9 +
  10 +import java.time.Duration;
  11 +
  12 +/**
  13 + * TCP 的 {@link IotDataRuleAction} 实现类
  14 + * <p>
  15 + * 负责将设备消息发送到外部 TCP 服务器
  16 + * 支持普通 TCP 和 SSL TCP 连接,支持 JSON 和 BINARY 数据格式
  17 + * 使用连接池管理 TCP 连接,提高性能和资源利用率
  18 + *
  19 + * @author HUIHUI
  20 + */
  21 +@Component
  22 +@Slf4j
  23 +public class IotTcpDataRuleAction extends
  24 + IotDataRuleCacheableAction<IotDataSinkTcpConfig, IotTcpClient> {
  25 +
  26 + private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(5);
  27 + private static final Duration SEND_TIMEOUT = Duration.ofSeconds(10);
  28 +
  29 + @Override
  30 + public Integer getType() {
  31 + return IotDataSinkTypeEnum.TCP.getType();
  32 + }
  33 +
  34 + @Override
  35 + protected IotTcpClient initProducer(IotDataSinkTcpConfig config) throws Exception {
  36 + // 1. 参数校验
  37 + if (config.getHost() == null || config.getHost().trim().isEmpty()) {
  38 + throw new IllegalArgumentException("TCP 服务器地址不能为空");
  39 + }
  40 + if (config.getPort() == null || config.getPort() <= 0 || config.getPort() > 65535) {
  41 + throw new IllegalArgumentException("TCP 服务器端口无效");
  42 + }
  43 +
  44 + // 2. 创建 TCP 客户端
  45 + IotTcpClient tcpClient = new IotTcpClient(
  46 + config.getHost(),
  47 + config.getPort(),
  48 + config.getConnectTimeoutMs(),
  49 + config.getReadTimeoutMs(),
  50 + config.getSsl(),
  51 + config.getSslCertPath(),
  52 + config.getDataFormat()
  53 + );
  54 +
  55 + // 3. 连接服务器
  56 + tcpClient.connect();
  57 +
  58 + log.info("[initProducer][TCP 客户端创建并连接成功,服务器: {}:{},SSL: {},数据格式: {}]",
  59 + config.getHost(), config.getPort(), config.getSsl(), config.getDataFormat());
  60 +
  61 + return tcpClient;
  62 + }
  63 +
  64 + @Override
  65 + protected void closeProducer(IotTcpClient producer) throws Exception {
  66 + if (producer != null) {
  67 + producer.close();
  68 + }
  69 + }
  70 +
  71 + @Override
  72 + protected void execute(IotDeviceMessage message, IotDataSinkTcpConfig config) throws Exception {
  73 + try {
  74 + // 1. 获取或创建 TCP 客户端
  75 + IotTcpClient tcpClient = getProducer(config);
  76 +
  77 + // 2. 检查连接状态,如果断开则重新连接
  78 + if (!tcpClient.isConnected()) {
  79 + log.warn("[execute][TCP 连接已断开,尝试重新连接,服务器: {}:{}]", config.getHost(), config.getPort());
  80 + tcpClient.connect();
  81 + }
  82 +
  83 + // 3. 发送消息并等待结果
  84 + tcpClient.sendMessage(message);
  85 +
  86 + // 4. 记录发送成功日志
  87 + log.info("[execute][message({}) config({}) 发送成功,TCP 服务器: {}:{}]",
  88 + message, config, config.getHost(), config.getPort());
  89 +
  90 + } catch (Exception e) {
  91 + log.error("[execute][message({}) config({}) 发送失败,TCP 服务器: {}:{}]",
  92 + message, config, config.getHost(), config.getPort(), e);
  93 + throw e;
  94 + }
  95 + }
  96 +
  97 +}
0 \ No newline at end of file 98 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/tcp/IotTcpClient.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.data.action.tcp;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +
  7 +import javax.net.ssl.SSLSocketFactory;
  8 +import java.io.BufferedReader;
  9 +import java.io.IOException;
  10 +import java.io.InputStreamReader;
  11 +import java.io.OutputStream;
  12 +import java.net.InetSocketAddress;
  13 +import java.net.Socket;
  14 +import java.nio.charset.StandardCharsets;
  15 +import java.util.concurrent.atomic.AtomicBoolean;
  16 +
  17 +/**
  18 + * IoT TCP 客户端
  19 + * <p>
  20 + * 负责与外部 TCP 服务器建立连接并发送设备消息
  21 + * 支持 JSON 和 BINARY 两种数据格式,支持 SSL 加密连接
  22 + *
  23 + * @author HUIHUI
  24 + */
  25 +@Slf4j
  26 +public class IotTcpClient {
  27 +
  28 + private final String host;
  29 + private final Integer port;
  30 + private final Integer connectTimeoutMs;
  31 + private final Integer readTimeoutMs;
  32 + private final Boolean ssl;
  33 + private final String sslCertPath;
  34 + private final String dataFormat;
  35 +
  36 + private Socket socket;
  37 + private OutputStream outputStream;
  38 + private BufferedReader reader;
  39 + private final AtomicBoolean connected = new AtomicBoolean(false);
  40 +
  41 + public IotTcpClient(String host, Integer port, Integer connectTimeoutMs, Integer readTimeoutMs,
  42 + Boolean ssl, String sslCertPath, String dataFormat) {
  43 + this.host = host;
  44 + this.port = port;
  45 + this.connectTimeoutMs = connectTimeoutMs != null ? connectTimeoutMs : 5000;
  46 + this.readTimeoutMs = readTimeoutMs != null ? readTimeoutMs : 10000;
  47 + this.ssl = ssl != null ? ssl : false;
  48 + this.sslCertPath = sslCertPath;
  49 + this.dataFormat = dataFormat != null ? dataFormat : "JSON";
  50 + }
  51 +
  52 + /**
  53 + * 连接到 TCP 服务器
  54 + */
  55 + public void connect() throws Exception {
  56 + if (connected.get()) {
  57 + log.warn("[connect][TCP 客户端已经连接,无需重复连接]");
  58 + return;
  59 + }
  60 +
  61 + try {
  62 + if (ssl) {
  63 + // SSL 连接
  64 + SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
  65 + socket = sslSocketFactory.createSocket();
  66 + } else {
  67 + // 普通连接
  68 + socket = new Socket();
  69 + }
  70 +
  71 + // 连接服务器
  72 + socket.connect(new InetSocketAddress(host, port), connectTimeoutMs);
  73 + socket.setSoTimeout(readTimeoutMs);
  74 +
  75 + // 获取输入输出流
  76 + outputStream = socket.getOutputStream();
  77 + reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
  78 +
  79 + connected.set(true);
  80 + log.info("[connect][TCP 客户端连接成功,服务器地址: {}:{}]", host, port);
  81 +
  82 + } catch (Exception e) {
  83 + close();
  84 + log.error("[connect][TCP 客户端连接失败,服务器地址: {}:{}]", host, port, e);
  85 + throw e;
  86 + }
  87 + }
  88 +
  89 + /**
  90 + * 发送设备消息
  91 + *
  92 + * @param message 设备消息
  93 + * @throws Exception 发送异常
  94 + */
  95 + public void sendMessage(IotDeviceMessage message) throws Exception {
  96 + if (!connected.get()) {
  97 + throw new IllegalStateException("TCP 客户端未连接");
  98 + }
  99 +
  100 + try {
  101 + String messageData;
  102 + if ("JSON".equalsIgnoreCase(dataFormat)) {
  103 + // JSON 格式
  104 + messageData = JsonUtils.toJsonString(message);
  105 + } else {
  106 + // BINARY 格式(这里简化为字符串,实际可能需要自定义二进制协议)
  107 + messageData = message.toString();
  108 + }
  109 +
  110 + // 发送消息
  111 + outputStream.write(messageData.getBytes(StandardCharsets.UTF_8));
  112 + outputStream.write('\n'); // 添加换行符作为消息分隔符
  113 + outputStream.flush();
  114 +
  115 + log.debug("[sendMessage][发送消息成功,设备 ID: {},消息长度: {}]",
  116 + message.getDeviceId(), messageData.length());
  117 +
  118 + } catch (Exception e) {
  119 + log.error("[sendMessage][发送消息失败,设备 ID: {}]", message.getDeviceId(), e);
  120 + throw e;
  121 + }
  122 + }
  123 +
  124 + /**
  125 + * 关闭连接
  126 + */
  127 + public void close() {
  128 + if (!connected.get()) {
  129 + return;
  130 + }
  131 +
  132 + try {
  133 + // 关闭资源
  134 + if (reader != null) {
  135 + try {
  136 + reader.close();
  137 + } catch (IOException e) {
  138 + log.warn("[close][关闭输入流失败]", e);
  139 + }
  140 + }
  141 + if (outputStream != null) {
  142 + try {
  143 + outputStream.close();
  144 + } catch (IOException e) {
  145 + log.warn("[close][关闭输出流失败]", e);
  146 + }
  147 + }
  148 + if (socket != null) {
  149 + try {
  150 + socket.close();
  151 + } catch (IOException e) {
  152 + log.warn("[close][关闭 Socket 失败]", e);
  153 + }
  154 + }
  155 +
  156 + connected.set(false);
  157 + log.info("[close][TCP 客户端连接已关闭,服务器地址: {}:{}]", host, port);
  158 +
  159 + } catch (Exception e) {
  160 + log.error("[close][关闭 TCP 客户端连接异常]", e);
  161 + }
  162 + }
  163 +
  164 + /**
  165 + * 检查连接状态
  166 + *
  167 + * @return 是否已连接
  168 + */
  169 + public boolean isConnected() {
  170 + return connected.get() && socket != null && !socket.isClosed();
  171 + }
  172 +
  173 + @Override
  174 + public String toString() {
  175 + return "IotTcpClient{" +
  176 + "host='" + host + '\'' +
  177 + ", port=" + port +
  178 + ", ssl=" + ssl +
  179 + ", dataFormat='" + dataFormat + '\'' +
  180 + ", connected=" + connected.get() +
  181 + '}';
  182 + }
  183 +
  184 +}
0 \ No newline at end of file 185 \ No newline at end of file
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; @@ -16,6 +16,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
16 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; 16 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
17 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; 17 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
18 import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; 18 import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper;
  19 +import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
19 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; 20 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
20 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 21 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
21 import cn.iocoder.yudao.module.iot.service.product.IotProductService; 22 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
@@ -24,6 +25,8 @@ import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatche @@ -24,6 +25,8 @@ import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatche
24 import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotSceneRuleTimerHandler; 25 import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotSceneRuleTimerHandler;
25 import jakarta.annotation.Resource; 26 import jakarta.annotation.Resource;
26 import lombok.extern.slf4j.Slf4j; 27 import lombok.extern.slf4j.Slf4j;
  28 +import org.springframework.cache.annotation.CacheEvict;
  29 +import org.springframework.cache.annotation.Cacheable;
27 import org.springframework.stereotype.Service; 30 import org.springframework.stereotype.Service;
28 import org.springframework.validation.annotation.Validated; 31 import org.springframework.validation.annotation.Validated;
29 32
@@ -60,6 +63,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @@ -60,6 +63,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
60 private IotSceneRuleTimerHandler timerHandler; 63 private IotSceneRuleTimerHandler timerHandler;
61 64
62 @Override 65 @Override
  66 + @CacheEvict(value = RedisKeyConstants.SCENE_RULE_LIST, allEntries = true)
63 public Long createSceneRule(IotSceneRuleSaveReqVO createReqVO) { 67 public Long createSceneRule(IotSceneRuleSaveReqVO createReqVO) {
64 IotSceneRuleDO sceneRule = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class); 68 IotSceneRuleDO sceneRule = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class);
65 sceneRuleMapper.insert(sceneRule); 69 sceneRuleMapper.insert(sceneRule);
@@ -71,6 +75,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @@ -71,6 +75,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
71 } 75 }
72 76
73 @Override 77 @Override
  78 + @CacheEvict(value = RedisKeyConstants.SCENE_RULE_LIST, allEntries = true)
74 public void updateSceneRule(IotSceneRuleSaveReqVO updateReqVO) { 79 public void updateSceneRule(IotSceneRuleSaveReqVO updateReqVO) {
75 // 校验存在 80 // 校验存在
76 validateSceneRuleExists(updateReqVO.getId()); 81 validateSceneRuleExists(updateReqVO.getId());
@@ -83,6 +88,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @@ -83,6 +88,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
83 } 88 }
84 89
85 @Override 90 @Override
  91 + @CacheEvict(value = RedisKeyConstants.SCENE_RULE_LIST, allEntries = true)
86 public void updateSceneRuleStatus(Long id, Integer status) { 92 public void updateSceneRuleStatus(Long id, Integer status) {
87 // 1. 校验存在 93 // 1. 校验存在
88 validateSceneRuleExists(id); 94 validateSceneRuleExists(id);
@@ -105,6 +111,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @@ -105,6 +111,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
105 } 111 }
106 112
107 @Override 113 @Override
  114 + @CacheEvict(value = RedisKeyConstants.SCENE_RULE_LIST, allEntries = true)
108 public void deleteSceneRule(Long id) { 115 public void deleteSceneRule(Long id) {
109 // 1. 校验存在 116 // 1. 校验存在
110 validateSceneRuleExists(id); 117 validateSceneRuleExists(id);
@@ -149,15 +156,12 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @@ -149,15 +156,12 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
149 return sceneRuleMapper.selectListByStatus(status); 156 return sceneRuleMapper.selectListByStatus(status);
150 } 157 }
151 158
152 - // TODO @puhui999:缓存待实现  
153 @Override 159 @Override
  160 + @Cacheable(value = RedisKeyConstants.SCENE_RULE_LIST, key = "#productId + '_' + #deviceId ")
154 @TenantIgnore // 忽略租户隔离:因为 IotSceneRuleMessageHandler 调用时,一般未传递租户,所以需要忽略 161 @TenantIgnore // 忽略租户隔离:因为 IotSceneRuleMessageHandler 调用时,一般未传递租户,所以需要忽略
155 public List<IotSceneRuleDO> getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId) { 162 public List<IotSceneRuleDO> getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId) {
156 // 1. 查询启用状态的规则场景 163 // 1. 查询启用状态的规则场景
157 - // TODO @puhui999:这里查询 enable 的;  
158 - List<IotSceneRuleDO> list = sceneRuleMapper.selectList();  
159 - List<IotSceneRuleDO> enabledList = filterList(list,  
160 - sceneRule -> CommonStatusEnum.isEnable(sceneRule.getStatus())); 164 + List<IotSceneRuleDO> enabledList = sceneRuleMapper.selectList(IotSceneRuleDO::getStatus, CommonStatusEnum.ENABLE.getStatus());
161 165
162 // 2. 根据 productKey 和 deviceName 进行匹配 166 // 2. 根据 productKey 和 deviceName 进行匹配
163 return filterList(enabledList, sceneRule -> { 167 return filterList(enabledList, sceneRule -> {
@@ -190,9 +194,10 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @@ -190,9 +194,10 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
190 194
191 @Override 195 @Override
192 public void executeSceneRuleByDevice(IotDeviceMessage message) { 196 public void executeSceneRuleByDevice(IotDeviceMessage message) {
193 - // TODO @puhui999:这里的 tenantId,通过设备获取;  
194 - TenantUtils.execute(message.getTenantId(), () -> {  
195 - // 1. 获得设备匹配的规则场景 197 + // 1.1 这里的 tenantId,通过设备获取;
  198 + IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId());
  199 + TenantUtils.execute(device.getTenantId(), () -> {
  200 + // 1.2 获得设备匹配的规则场景
196 List<IotSceneRuleDO> sceneRules = getMatchedSceneRuleListByMessage(message); 201 List<IotSceneRuleDO> sceneRules = getMatchedSceneRuleListByMessage(message);
197 if (CollUtil.isEmpty(sceneRules)) { 202 if (CollUtil.isEmpty(sceneRules)) {
198 return; 203 return;
@@ -238,14 +243,14 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @@ -238,14 +243,14 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
238 // 1. 匹配设备 243 // 1. 匹配设备
239 // TODO 缓存 @puhui999:可能需要 getSelf() 244 // TODO 缓存 @puhui999:可能需要 getSelf()
240 // 1.1 通过 deviceId 获取设备信息 245 // 1.1 通过 deviceId 获取设备信息
241 - IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId()); 246 + IotDeviceDO device = getSelf().deviceService.getDeviceFromCache(message.getDeviceId());
242 if (device == null) { 247 if (device == null) {
243 log.warn("[getMatchedSceneRuleListByMessage][设备({}) 不存在]", message.getDeviceId()); 248 log.warn("[getMatchedSceneRuleListByMessage][设备({}) 不存在]", message.getDeviceId());
244 return List.of(); 249 return List.of();
245 } 250 }
246 251
247 // 1.2 通过 productId 获取产品信息 252 // 1.2 通过 productId 获取产品信息
248 - IotProductDO product = productService.getProductFromCache(device.getProductId()); 253 + IotProductDO product = getSelf().productService.getProductFromCache(device.getProductId());
249 if (product == null) { 254 if (product == null) {
250 log.warn("[getMatchedSceneRuleListByMessage][产品({}) 不存在]", device.getProductId()); 255 log.warn("[getMatchedSceneRuleListByMessage][产品({}) 不存在]", device.getProductId());
251 return List.of(); 256 return List.of();
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java renamed to 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; @@ -16,7 +16,6 @@ import java.time.LocalTime;
16 import java.time.format.DateTimeFormatter; 16 import java.time.format.DateTimeFormatter;
17 import java.util.List; 17 import java.util.List;
18 18
19 -// TODO @puhui999:是不是 IoT 的前缀,都加下哈;  
20 /** 19 /**
21 * 当前时间条件匹配器:处理时间相关的子条件匹配逻辑 20 * 当前时间条件匹配器:处理时间相关的子条件匹配逻辑
22 * 21 *
@@ -24,7 +23,7 @@ import java.util.List; @@ -24,7 +23,7 @@ import java.util.List;
24 */ 23 */
25 @Component 24 @Component
26 @Slf4j 25 @Slf4j
27 -public class CurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher { 26 +public class IotCurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher {
28 27
29 /** 28 /**
30 * 时间格式化器 - HH:mm:ss 29 * 时间格式化器 - HH:mm:ss
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java renamed to 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; @@ -15,7 +15,7 @@ import org.springframework.stereotype.Component;
15 * @author HUIHUI 15 * @author HUIHUI
16 */ 16 */
17 @Component 17 @Component
18 -public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatcher { 18 +public class IotDevicePropertyConditionMatcher implements IotSceneRuleConditionMatcher {
19 19
20 @Override 20 @Override
21 public IotSceneRuleConditionTypeEnum getSupportedConditionType() { 21 public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java renamed to 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; @@ -13,7 +13,7 @@ import org.springframework.stereotype.Component;
13 * @author HUIHUI 13 * @author HUIHUI
14 */ 14 */
15 @Component 15 @Component
16 -public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher { 16 +public class IotDeviceStateConditionMatcher implements IotSceneRuleConditionMatcher {
17 17
18 @Override 18 @Override
19 public IotSceneRuleConditionTypeEnum getSupportedConditionType() { 19 public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java renamed to 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; @@ -15,7 +15,7 @@ import org.springframework.stereotype.Component;
15 * @author HUIHUI 15 * @author HUIHUI
16 */ 16 */
17 @Component 17 @Component
18 -public class DeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher { 18 +public class IotDeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher {
19 19
20 @Override 20 @Override
21 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { 21 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java renamed to 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; @@ -14,7 +14,7 @@ import org.springframework.stereotype.Component;
14 * @author HUIHUI 14 * @author HUIHUI
15 */ 15 */
16 @Component 16 @Component
17 -public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatcher { 17 +public class IotDevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatcher {
18 18
19 @Override 19 @Override
20 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { 20 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java renamed to 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; @@ -14,7 +14,7 @@ import org.springframework.stereotype.Component;
14 * @author HUIHUI 14 * @author HUIHUI
15 */ 15 */
16 @Component 16 @Component
17 -public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher { 17 +public class IotDeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher {
18 18
19 @Override 19 @Override
20 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { 20 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
@@ -44,7 +44,7 @@ public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMat @@ -44,7 +44,7 @@ public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMat
44 44
45 // 2. 对于服务调用触发器,通常只需要匹配服务标识符即可 45 // 2. 对于服务调用触发器,通常只需要匹配服务标识符即可
46 // 不需要检查操作符和值,因为服务调用本身就是触发条件 46 // 不需要检查操作符和值,因为服务调用本身就是触发条件
47 - // TODO @puhui999: 服务调用时校验输入参数是否匹配条件 47 + // TODO @puhui999: 服务调用时校验输入参数是否匹配条件?
48 IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); 48 IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
49 return true; 49 return true;
50 } 50 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java renamed to 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; @@ -14,7 +14,7 @@ import org.springframework.stereotype.Component;
14 * @author HUIHUI 14 * @author HUIHUI
15 */ 15 */
16 @Component 16 @Component
17 -public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher { 17 +public class IotDeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher {
18 18
19 @Override 19 @Override
20 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { 20 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java renamed to 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; @@ -16,7 +16,7 @@ import org.springframework.stereotype.Component;
16 * @author HUIHUI 16 * @author HUIHUI
17 */ 17 */
18 @Component 18 @Component
19 -public class TimerTriggerMatcher implements IotSceneRuleTriggerMatcher { 19 +public class IotTimerTriggerMatcher implements IotSceneRuleTriggerMatcher {
20 20
21 @Override 21 @Override
22 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { 22 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleActionTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.data.action;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkTcpConfig;
  6 +import cn.iocoder.yudao.module.iot.service.rule.data.action.tcp.IotTcpClient;
  7 +import org.junit.jupiter.api.BeforeEach;
  8 +import org.junit.jupiter.api.Test;
  9 +import org.mockito.Mock;
  10 +import org.mockito.MockitoAnnotations;
  11 +
  12 +import static org.junit.jupiter.api.Assertions.*;
  13 +import static org.mockito.Mockito.*;
  14 +
  15 +/**
  16 + * {@link IotTcpDataRuleAction} 的单元测试
  17 + *
  18 + * @author HUIHUI
  19 + */
  20 +class IotTcpDataRuleActionTest {
  21 +
  22 + private IotTcpDataRuleAction tcpDataRuleAction;
  23 +
  24 + @Mock
  25 + private IotTcpClient mockTcpClient;
  26 +
  27 + @BeforeEach
  28 + void setUp() {
  29 + MockitoAnnotations.openMocks(this);
  30 + tcpDataRuleAction = new IotTcpDataRuleAction();
  31 + }
  32 +
  33 + @Test
  34 + void testGetType() {
  35 + // 准备参数
  36 + Integer expectedType = 2; // 数据接收类型枚举中 TCP 类型的值
  37 +
  38 + // 调用方法
  39 + Integer actualType = tcpDataRuleAction.getType();
  40 +
  41 + // 断言结果
  42 + assertEquals(expectedType, actualType);
  43 + }
  44 +
  45 + @Test
  46 + void testInitProducer_Success() throws Exception {
  47 + // 准备参数
  48 + IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
  49 + config.setHost("localhost");
  50 + config.setPort(8080);
  51 + config.setDataFormat("JSON");
  52 + config.setSsl(false);
  53 +
  54 + // 调用方法 & 断言结果
  55 + // 此测试需要实际的 TCP 连接,在单元测试中可能不可用
  56 + // 目前我们只验证配置是否有效
  57 + assertNotNull(config.getHost());
  58 + assertTrue(config.getPort() > 0 && config.getPort() <= 65535);
  59 + }
  60 +
  61 + @Test
  62 + void testInitProducer_InvalidHost() {
  63 + // 准备参数
  64 + IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
  65 + config.setHost("");
  66 + config.setPort(8080);
  67 +
  68 + // 调用方法 & 断言结果
  69 + IotTcpDataRuleAction action = new IotTcpDataRuleAction();
  70 +
  71 + // 测试验证逻辑(通常在 initProducer 方法中)
  72 + assertThrows(IllegalArgumentException.class, () -> {
  73 + if (config.getHost() == null || config.getHost().trim().isEmpty()) {
  74 + throw new IllegalArgumentException("TCP 服务器地址不能为空");
  75 + }
  76 + });
  77 + }
  78 +
  79 + @Test
  80 + void testInitProducer_InvalidPort() {
  81 + // 准备参数
  82 + IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
  83 + config.setHost("localhost");
  84 + config.setPort(-1);
  85 +
  86 + // 调用方法 & 断言结果
  87 + assertThrows(IllegalArgumentException.class, () -> {
  88 + if (config.getPort() == null || config.getPort() <= 0 || config.getPort() > 65535) {
  89 + throw new IllegalArgumentException("TCP 服务器端口无效");
  90 + }
  91 + });
  92 + }
  93 +
  94 + @Test
  95 + void testCloseProducer() throws Exception {
  96 + // 准备参数
  97 + IotTcpClient client = mock(IotTcpClient.class);
  98 +
  99 + // 调用方法
  100 + tcpDataRuleAction.closeProducer(client);
  101 +
  102 + // 断言结果
  103 + verify(client, times(1)).close();
  104 + }
  105 +
  106 + @Test
  107 + void testExecute_WithValidConfig() {
  108 + // 准备参数
  109 + IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.report",
  110 + "{\"temperature\": 25.5, \"humidity\": 60}");
  111 +
  112 + IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
  113 + config.setHost("localhost");
  114 + config.setPort(8080);
  115 + config.setDataFormat("JSON");
  116 +
  117 + // 调用方法 & 断言结果
  118 + // 通常这需要实际的 TCP 连接
  119 + // 在单元测试中,我们只验证输入参数
  120 + assertNotNull(message);
  121 + assertNotNull(config);
  122 + assertNotNull(config.getHost());
  123 + assertTrue(config.getPort() > 0);
  124 + }
  125 +
  126 + @Test
  127 + void testConfig_DefaultValues() {
  128 + // 准备参数
  129 + IotDataSinkTcpConfig config = new IotDataSinkTcpConfig();
  130 +
  131 + // 调用方法 & 断言结果
  132 + // 验证默认值
  133 + assertEquals("JSON", config.getDataFormat());
  134 + assertEquals(5000, config.getConnectTimeoutMs());
  135 + assertEquals(10000, config.getReadTimeoutMs());
  136 + assertEquals(false, config.getSsl());
  137 + assertEquals(30000L, config.getHeartbeatIntervalMs());
  138 + assertEquals(5000L, config.getReconnectIntervalMs());
  139 + assertEquals(3, config.getMaxReconnectAttempts());
  140 + }
  141 +
  142 + @Test
  143 + void testMessageSerialization() {
  144 + // 准备参数
  145 + IotDeviceMessage message = IotDeviceMessage.builder()
  146 + .deviceId(123L)
  147 + .method("thing.property.report")
  148 + .params("{\"temperature\": 25.5}")
  149 + .build();
  150 +
  151 + // 调用方法
  152 + String json = JsonUtils.toJsonString(message);
  153 +
  154 + // 断言结果
  155 + assertNotNull(json);
  156 + assertTrue(json.contains("\"deviceId\":123"));
  157 + assertTrue(json.contains("\"method\":\"thing.property.report\""));
  158 + assertTrue(json.contains("\"temperature\":25.5"));
  159 + }
  160 +
  161 +}
0 \ No newline at end of file 162 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java renamed to 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; @@ -6,7 +6,6 @@ import org.springframework.context.annotation.Bean;
6 import org.springframework.context.annotation.Configuration; 6 import org.springframework.context.annotation.Configuration;
7 import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; 7 import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
8 8
9 -// TODO @puhui999:建议改成 IotBaseConditionMatcherTest  
10 /** 9 /**
11 * Matcher 测试基类 10 * Matcher 测试基类
12 * 提供通用的 Spring 测试配置 11 * 提供通用的 Spring 测试配置
@@ -14,7 +13,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -14,7 +13,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
14 * @author HUIHUI 13 * @author HUIHUI
15 */ 14 */
16 @SpringJUnitConfig 15 @SpringJUnitConfig
17 -public abstract class BaseMatcherTest { 16 +public abstract class IotBaseConditionMatcherTest {
18 17
19 /** 18 /**
20 * 注入一下 SpringUtil,解析 EL 表达式时需要 19 * 注入一下 SpringUtil,解析 EL 表达式时需要
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java renamed to 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; @@ -4,7 +4,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
4 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; 4 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
5 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; 5 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
6 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; 6 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
7 -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; 7 +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
8 import org.junit.jupiter.api.BeforeEach; 8 import org.junit.jupiter.api.BeforeEach;
9 import org.junit.jupiter.api.Test; 9 import org.junit.jupiter.api.Test;
10 10
@@ -16,17 +16,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString @@ -16,17 +16,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
16 import static org.junit.jupiter.api.Assertions.*; 16 import static org.junit.jupiter.api.Assertions.*;
17 17
18 /** 18 /**
19 - * {@link CurrentTimeConditionMatcher} 的单元测试 19 + * {@link IotCurrentTimeConditionMatcher} 的单元测试
20 * 20 *
21 * @author HUIHUI 21 * @author HUIHUI
22 */ 22 */
23 -public class CurrentTimeConditionMatcherTest extends BaseMatcherTest { 23 +public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherTest {
24 24
25 - private CurrentTimeConditionMatcher matcher; 25 + private IotCurrentTimeConditionMatcher matcher;
26 26
27 @BeforeEach 27 @BeforeEach
28 public void setUp() { 28 public void setUp() {
29 - matcher = new CurrentTimeConditionMatcher(); 29 + matcher = new IotCurrentTimeConditionMatcher();
30 } 30 }
31 31
32 @Test 32 @Test
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java renamed to 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; @@ -4,7 +4,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
4 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; 4 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
5 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; 5 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
6 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; 6 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
7 -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; 7 +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
8 import org.junit.jupiter.api.BeforeEach; 8 import org.junit.jupiter.api.BeforeEach;
9 import org.junit.jupiter.api.Test; 9 import org.junit.jupiter.api.Test;
10 10
@@ -15,17 +15,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId @@ -15,17 +15,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId
15 import static org.junit.jupiter.api.Assertions.*; 15 import static org.junit.jupiter.api.Assertions.*;
16 16
17 /** 17 /**
18 - * {@link DevicePropertyConditionMatcher} 的单元测试 18 + * {@link IotDevicePropertyConditionMatcher} 的单元测试
19 * 19 *
20 * @author HUIHUI 20 * @author HUIHUI
21 */ 21 */
22 -public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { 22 +public class IotDevicePropertyConditionMatcherTest extends IotBaseConditionMatcherTest {
23 23
24 - private DevicePropertyConditionMatcher matcher; 24 + private IotDevicePropertyConditionMatcher matcher;
25 25
26 @BeforeEach 26 @BeforeEach
27 public void setUp() { 27 public void setUp() {
28 - matcher = new DevicePropertyConditionMatcher(); 28 + matcher = new IotDevicePropertyConditionMatcher();
29 } 29 }
30 30
31 @Test 31 @Test
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java renamed to 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; @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; 5 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
6 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; 6 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
7 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; 7 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
8 -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; 8 +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
9 import org.junit.jupiter.api.BeforeEach; 9 import org.junit.jupiter.api.BeforeEach;
10 import org.junit.jupiter.api.Test; 10 import org.junit.jupiter.api.Test;
11 11
@@ -14,17 +14,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString @@ -14,17 +14,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
14 import static org.junit.jupiter.api.Assertions.*; 14 import static org.junit.jupiter.api.Assertions.*;
15 15
16 /** 16 /**
17 - * {@link DeviceStateConditionMatcher} 的单元测试 17 + * {@link IotDeviceStateConditionMatcher} 的单元测试
18 * 18 *
19 * @author HUIHUI 19 * @author HUIHUI
20 */ 20 */
21 -public class DeviceStateConditionMatcherTest extends BaseMatcherTest { 21 +public class IotDeviceStateConditionMatcherTest extends IotBaseConditionMatcherTest {
22 22
23 - private DeviceStateConditionMatcher matcher; 23 + private IotDeviceStateConditionMatcher matcher;
24 24
25 @BeforeEach 25 @BeforeEach
26 public void setUp() { 26 public void setUp() {
27 - matcher = new DeviceStateConditionMatcher(); 27 + matcher = new IotDeviceStateConditionMatcher();
28 } 28 }
29 29
30 @Test 30 @Test
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java renamed to 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; @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; 6 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
7 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; 7 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
8 -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; 8 +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
9 import org.junit.jupiter.api.BeforeEach; 9 import org.junit.jupiter.api.BeforeEach;
10 import org.junit.jupiter.api.Test; 10 import org.junit.jupiter.api.Test;
11 11
@@ -18,17 +18,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString @@ -18,17 +18,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
18 import static org.junit.jupiter.api.Assertions.*; 18 import static org.junit.jupiter.api.Assertions.*;
19 19
20 /** 20 /**
21 - * {@link DeviceEventPostTriggerMatcher} 的单元测试 21 + * {@link IotDeviceEventPostTriggerMatcher} 的单元测试
22 * 22 *
23 * @author HUIHUI 23 * @author HUIHUI
24 */ 24 */
25 -public class DeviceEventPostTriggerMatcherTest extends BaseMatcherTest { 25 +public class IotDeviceEventPostTriggerMatcherTest extends IotBaseConditionMatcherTest {
26 26
27 - private DeviceEventPostTriggerMatcher matcher; 27 + private IotDeviceEventPostTriggerMatcher matcher;
28 28
29 @BeforeEach 29 @BeforeEach
30 public void setUp() { 30 public void setUp() {
31 - matcher = new DeviceEventPostTriggerMatcher(); 31 + matcher = new IotDeviceEventPostTriggerMatcher();
32 } 32 }
33 33
34 @Test 34 @Test
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java renamed to 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; @@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; 6 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
7 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; 7 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
8 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; 8 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
9 -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; 9 +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
10 import org.junit.jupiter.api.BeforeEach; 10 import org.junit.jupiter.api.BeforeEach;
11 import org.junit.jupiter.api.Test; 11 import org.junit.jupiter.api.Test;
12 12
@@ -20,17 +20,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString @@ -20,17 +20,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
20 import static org.junit.jupiter.api.Assertions.*; 20 import static org.junit.jupiter.api.Assertions.*;
21 21
22 /** 22 /**
23 - * {@link DevicePropertyPostTriggerMatcher} 的单元测试 23 + * {@link IotDevicePropertyPostTriggerMatcher} 的单元测试
24 * 24 *
25 * @author HUIHUI 25 * @author HUIHUI
26 */ 26 */
27 -public class DevicePropertyPostTriggerMatcherTest extends BaseMatcherTest { 27 +public class IotDevicePropertyPostTriggerMatcherTest extends IotBaseConditionMatcherTest {
28 28
29 - private DevicePropertyPostTriggerMatcher matcher; 29 + private IotDevicePropertyPostTriggerMatcher matcher;
30 30
31 @BeforeEach 31 @BeforeEach
32 public void setUp() { 32 public void setUp() {
33 - matcher = new DevicePropertyPostTriggerMatcher(); 33 + matcher = new IotDevicePropertyPostTriggerMatcher();
34 } 34 }
35 35
36 @Test 36 @Test
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java renamed to 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; @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; 6 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
7 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; 7 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
8 -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; 8 +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
9 import org.junit.jupiter.api.BeforeEach; 9 import org.junit.jupiter.api.BeforeEach;
10 import org.junit.jupiter.api.Test; 10 import org.junit.jupiter.api.Test;
11 11
@@ -18,17 +18,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString @@ -18,17 +18,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
18 import static org.junit.jupiter.api.Assertions.*; 18 import static org.junit.jupiter.api.Assertions.*;
19 19
20 /** 20 /**
21 - * {@link DeviceServiceInvokeTriggerMatcher} 的单元测试 21 + * {@link IotDeviceServiceInvokeTriggerMatcher} 的单元测试
22 * 22 *
23 * @author HUIHUI 23 * @author HUIHUI
24 */ 24 */
25 -public class DeviceServiceInvokeTriggerMatcherTest extends BaseMatcherTest { 25 +public class IotDeviceServiceInvokeTriggerMatcherTest extends IotBaseConditionMatcherTest {
26 26
27 - private DeviceServiceInvokeTriggerMatcher matcher; 27 + private IotDeviceServiceInvokeTriggerMatcher matcher;
28 28
29 @BeforeEach 29 @BeforeEach
30 public void setUp() { 30 public void setUp() {
31 - matcher = new DeviceServiceInvokeTriggerMatcher(); 31 + matcher = new IotDeviceServiceInvokeTriggerMatcher();
32 } 32 }
33 33
34 @Test 34 @Test
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java renamed to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java
1 package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; 1 package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
2 2
3 -import cn.hutool.extra.spring.SpringUtil;  
4 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 3 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
5 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; 4 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
7 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; 6 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
8 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; 7 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
9 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; 8 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  9 +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
10 import org.junit.jupiter.api.BeforeEach; 10 import org.junit.jupiter.api.BeforeEach;
11 import org.junit.jupiter.api.Test; 11 import org.junit.jupiter.api.Test;
12 -import org.springframework.context.annotation.Bean;  
13 -import org.springframework.context.annotation.Configuration;  
14 -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;  
15 12
16 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; 13 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
17 import static org.junit.jupiter.api.Assertions.*; 14 import static org.junit.jupiter.api.Assertions.*;
18 15
19 /** 16 /**
20 - * {@link DeviceStateUpdateTriggerMatcher} 的单元测试 17 + * {@link IotDeviceStateUpdateTriggerMatcher} 的单元测试
21 * 18 *
22 * @author HUIHUI 19 * @author HUIHUI
23 */ 20 */
24 -@SpringJUnitConfig(DeviceStateUpdateTriggerMatcherTest.TestConfig.class)  
25 -public class DeviceStateUpdateTriggerMatcherTest {  
26 -  
27 - @Configuration  
28 - static class TestConfig {  
29 - @Bean  
30 - public SpringUtil springUtil() {  
31 - return new SpringUtil();  
32 - }  
33 - } 21 +public class IotDeviceStateUpdateTriggerMatcherTest extends IotBaseConditionMatcherTest {
34 22
35 - private DeviceStateUpdateTriggerMatcher matcher; 23 + private IotDeviceStateUpdateTriggerMatcher matcher;
36 24
37 @BeforeEach 25 @BeforeEach
38 public void setUp() { 26 public void setUp() {
39 - matcher = new DeviceStateUpdateTriggerMatcher(); 27 + matcher = new IotDeviceStateUpdateTriggerMatcher();
40 } 28 }
41 29
42 @Test 30 @Test
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java renamed to 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; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
4 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; 4 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
5 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; 5 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
6 -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; 6 +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest;
7 import org.junit.jupiter.api.BeforeEach; 7 import org.junit.jupiter.api.BeforeEach;
8 import org.junit.jupiter.api.Test; 8 import org.junit.jupiter.api.Test;
9 9
@@ -12,17 +12,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString @@ -12,17 +12,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString
12 import static org.junit.jupiter.api.Assertions.*; 12 import static org.junit.jupiter.api.Assertions.*;
13 13
14 /** 14 /**
15 - * {@link TimerTriggerMatcher} 的单元测试 15 + * {@link IotTimerTriggerMatcher} 的单元测试
16 * 16 *
17 * @author HUIHUI 17 * @author HUIHUI
18 */ 18 */
19 -public class TimerTriggerMatcherTest extends BaseMatcherTest { 19 +public class IotTimerTriggerMatcherTest extends IotBaseConditionMatcherTest {
20 20
21 - private TimerTriggerMatcher matcher; 21 + private IotTimerTriggerMatcher matcher;
22 22
23 @BeforeEach 23 @BeforeEach
24 public void setUp() { 24 public void setUp() {
25 - matcher = new TimerTriggerMatcher(); 25 + matcher = new IotTimerTriggerMatcher();
26 } 26 }
27 27
28 @Test 28 @Test
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandlerTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.timer;
  2 +
  3 +import cn.hutool.core.collection.ListUtil;
  4 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  5 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  6 +import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager;
  7 +import cn.iocoder.yudao.module.iot.job.rule.IotSceneRuleJob;
  8 +import org.junit.jupiter.api.BeforeEach;
  9 +import org.junit.jupiter.api.Test;
  10 +import org.junit.jupiter.api.extension.ExtendWith;
  11 +import org.mockito.InjectMocks;
  12 +import org.mockito.Mock;
  13 +import org.mockito.junit.jupiter.MockitoExtension;
  14 +import org.quartz.SchedulerException;
  15 +
  16 +import static org.mockito.ArgumentMatchers.any;
  17 +import static org.mockito.ArgumentMatchers.eq;
  18 +import static org.mockito.Mockito.*;
  19 +
  20 +/**
  21 + * {@link IotSceneRuleTimerHandler} 的单元测试类
  22 + *
  23 + * @author HUIHUI
  24 + */
  25 +@ExtendWith(MockitoExtension.class)
  26 +public class IotSceneRuleTimerHandlerTest {
  27 +
  28 + @Mock
  29 + private IotSchedulerManager schedulerManager;
  30 +
  31 + @InjectMocks
  32 + private IotSceneRuleTimerHandler timerHandler;
  33 +
  34 + @BeforeEach
  35 + void setUp() {
  36 + // 重置 Mock 对象
  37 + reset(schedulerManager);
  38 + }
  39 +
  40 + @Test
  41 + public void testRegisterTimerTriggers_success() throws SchedulerException {
  42 + // 准备参数
  43 + Long sceneRuleId = 1L;
  44 + IotSceneRuleDO sceneRule = new IotSceneRuleDO();
  45 + sceneRule.setId(sceneRuleId);
  46 + sceneRule.setStatus(0); // 0 表示启用
  47 + // 创建定时触发器
  48 + IotSceneRuleDO.Trigger timerTrigger = new IotSceneRuleDO.Trigger();
  49 + timerTrigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
  50 + timerTrigger.setCronExpression("0 0 12 * * ?"); // 每天中午12点
  51 + sceneRule.setTriggers(ListUtil.toList(timerTrigger));
  52 +
  53 + // 调用
  54 + timerHandler.registerTimerTriggers(sceneRule);
  55 +
  56 + // 验证
  57 + verify(schedulerManager, times(1)).addOrUpdateJob(
  58 + eq(IotSceneRuleJob.class),
  59 + eq("iot_scene_rule_timer_" + sceneRuleId),
  60 + eq("0 0 12 * * ?"),
  61 + eq(IotSceneRuleJob.buildJobDataMap(sceneRuleId))
  62 + );
  63 + }
  64 +
  65 + @Test
  66 + public void testRegisterTimerTriggers_noTimerTrigger() throws SchedulerException {
  67 + // 准备参数 - 没有定时触发器
  68 + IotSceneRuleDO sceneRule = new IotSceneRuleDO();
  69 + sceneRule.setStatus(0); // 0 表示启用
  70 + // 创建非定时触发器
  71 + IotSceneRuleDO.Trigger deviceTrigger = new IotSceneRuleDO.Trigger();
  72 + deviceTrigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType());
  73 + sceneRule.setTriggers(ListUtil.toList(deviceTrigger));
  74 +
  75 + // 调用
  76 + timerHandler.registerTimerTriggers(sceneRule);
  77 +
  78 + // 验证 - 不应该调用调度器
  79 + verify(schedulerManager, never()).addOrUpdateJob(any(), any(), any(), any());
  80 + }
  81 +
  82 + @Test
  83 + public void testRegisterTimerTriggers_emptyCronExpression() throws SchedulerException {
  84 + // 准备参数 - CRON 表达式为空
  85 + Long sceneRuleId = 2L;
  86 + IotSceneRuleDO sceneRule = new IotSceneRuleDO();
  87 + sceneRule.setId(sceneRuleId);
  88 + sceneRule.setStatus(0); // 0 表示启用
  89 + // 创建定时触发器但没有 CRON 表达式
  90 + IotSceneRuleDO.Trigger timerTrigger = new IotSceneRuleDO.Trigger();
  91 + timerTrigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
  92 + timerTrigger.setCronExpression(""); // 空的 CRON 表达式
  93 + sceneRule.setTriggers(ListUtil.toList(timerTrigger));
  94 +
  95 + // 调用
  96 + timerHandler.registerTimerTriggers(sceneRule);
  97 +
  98 + // 验证 - 不应该调用调度器
  99 + verify(schedulerManager, never()).addOrUpdateJob(any(), any(), any(), any());
  100 + }
  101 +
  102 + @Test
  103 + public void testUnregisterTimerTriggers_success() throws SchedulerException {
  104 + // 准备参数
  105 + Long sceneRuleId = 3L;
  106 +
  107 + // 调用
  108 + timerHandler.unregisterTimerTriggers(sceneRuleId);
  109 +
  110 + // 验证
  111 + verify(schedulerManager, times(1)).deleteJob("iot_scene_rule_timer_" + sceneRuleId);
  112 + }
  113 +
  114 + @Test
  115 + public void testPauseTimerTriggers_success() throws SchedulerException {
  116 + // 准备参数
  117 + Long sceneRuleId = 4L;
  118 +
  119 + // 调用
  120 + timerHandler.pauseTimerTriggers(sceneRuleId);
  121 +
  122 + // 验证
  123 + verify(schedulerManager, times(1)).pauseJob("iot_scene_rule_timer_" + sceneRuleId);
  124 + }
  125 +
  126 +}