Commit c1657149a85d8c5719ec444da6ff3529c9077e3b
Committed by
Gitee
!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 | +} |