From 796d69b241a229510a0ecf39d6085c085855e5c3 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 1 Sep 2025 15:17:06 +0800 Subject: [PATCH 01/11] =?UTF-8?q?refactor:=20=E3=80=90IoT=20=E7=89=A9?= =?UTF-8?q?=E8=81=94=E7=BD=91=E3=80=91test=20BaseMockitoUnitTest=20?= =?UTF-8?q?=E2=86=92=20BaseMatcherTest=EF=BC=8C=E6=94=AF=E6=8C=81=20Spring?= =?UTF-8?q?=20=E4=B8=8A=E4=B8=8B=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../matcher/IotSceneRuleMatcherHelper.java | 2 +- .../rule/scene/matcher/BaseMatcherTest.java | 32 +++++++++++++++++++ .../CurrentTimeConditionMatcherTest.java | 12 ++++--- .../DevicePropertyConditionMatcherTest.java | 12 ++++--- .../DeviceStateConditionMatcherTest.java | 12 ++++--- .../DeviceEventPostTriggerMatcherTest.java | 12 ++++--- .../DevicePropertyPostTriggerMatcherTest.java | 12 ++++--- ...DeviceServiceInvokeTriggerMatcherTest.java | 12 ++++--- .../DeviceStateUpdateTriggerMatcherTest.java | 24 +++++++++++--- .../trigger/TimerTriggerMatcherTest.java | 12 ++++--- 10 files changed, 109 insertions(+), 33 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java index 7175e37a7e..db111e4472 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java @@ -91,7 +91,7 @@ public final class IotSceneRuleMatcherHelper { Map springExpressionVariables = new HashMap<>(); // 设置源值 - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue); + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, StrUtil.toString(sourceValue)); // 处理参数值 if (StrUtil.isNotBlank(paramValue)) { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java new file mode 100644 index 0000000000..d73c926efa --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +/** + * Matcher 测试基类 + * 提供通用的 Spring 测试配置 + * + * @author HUIHUI + */ +@SpringJUnitConfig +public abstract class BaseMatcherTest { + + /** + * 注入一下 SpringUtil,解析 EL 表达式时需要 + * {@link SpringExpressionUtils#parseExpression} + */ + @Configuration + static class TestConfig { + + @Bean + public SpringUtil springUtil() { + return new SpringUtil(); + } + + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java index 4b4bdfd029..f14da99a56 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java @@ -1,12 +1,12 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import java.time.LocalDateTime; import java.time.ZoneOffset; @@ -20,11 +20,15 @@ import static org.junit.jupiter.api.Assertions.*; * * @author HUIHUI */ -public class CurrentTimeConditionMatcherTest extends BaseMockitoUnitTest { +public class CurrentTimeConditionMatcherTest extends BaseMatcherTest { - @InjectMocks private CurrentTimeConditionMatcher matcher; + @BeforeEach + public void setUp() { + matcher = new CurrentTimeConditionMatcher(); + } + @Test public void testGetSupportedConditionType() { // 调用 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java index c4edf34361..9c34d86216 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java @@ -1,13 +1,13 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import java.util.HashMap; import java.util.Map; @@ -21,11 +21,15 @@ import static org.junit.jupiter.api.Assertions.*; * * @author HUIHUI */ -public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { +public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { - @InjectMocks private DevicePropertyConditionMatcher matcher; + @BeforeEach + public void setUp() { + matcher = new DevicePropertyConditionMatcher(); + } + @Test public void testGetSupportedConditionType() { // 调用 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java index 25ea571528..d54575ba41 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java @@ -1,13 +1,13 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; @@ -18,11 +18,15 @@ import static org.junit.jupiter.api.Assertions.*; * * @author HUIHUI */ -public class DeviceStateConditionMatcherTest extends BaseMockitoUnitTest { +public class DeviceStateConditionMatcherTest extends BaseMatcherTest { - @InjectMocks private DeviceStateConditionMatcher matcher; + @BeforeEach + public void setUp() { + matcher = new DeviceStateConditionMatcher(); + } + @Test public void testGetSupportedConditionType() { // 调用 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java index 1ed8f1c48f..4569d439cc 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java @@ -1,13 +1,13 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import java.util.HashMap; import java.util.Map; @@ -22,11 +22,15 @@ import static org.junit.jupiter.api.Assertions.*; * * @author HUIHUI */ -public class DeviceEventPostTriggerMatcherTest extends BaseMockitoUnitTest { +public class DeviceEventPostTriggerMatcherTest extends BaseMatcherTest { - @InjectMocks private DeviceEventPostTriggerMatcher matcher; + @BeforeEach + public void setUp() { + matcher = new DeviceEventPostTriggerMatcher(); + } + @Test public void testGetSupportedTriggerType_success() { // 准备参数 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java index 2bed7fa631..eb191eb927 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java @@ -1,14 +1,14 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import java.util.HashMap; import java.util.Map; @@ -24,11 +24,15 @@ import static org.junit.jupiter.api.Assertions.*; * * @author HUIHUI */ -public class DevicePropertyPostTriggerMatcherTest extends BaseMockitoUnitTest { +public class DevicePropertyPostTriggerMatcherTest extends BaseMatcherTest { - @InjectMocks private DevicePropertyPostTriggerMatcher matcher; + @BeforeEach + public void setUp() { + matcher = new DevicePropertyPostTriggerMatcher(); + } + @Test public void testGetSupportedTriggerType_success() { // 准备参数 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java index a9348456f4..cfa36605f8 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java @@ -1,13 +1,13 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import java.util.HashMap; import java.util.Map; @@ -22,11 +22,15 @@ import static org.junit.jupiter.api.Assertions.*; * * @author HUIHUI */ -public class DeviceServiceInvokeTriggerMatcherTest extends BaseMockitoUnitTest { +public class DeviceServiceInvokeTriggerMatcherTest extends BaseMatcherTest { - @InjectMocks private DeviceServiceInvokeTriggerMatcher matcher; + @BeforeEach + public void setUp() { + matcher = new DeviceServiceInvokeTriggerMatcher(); + } + @Test public void testGetSupportedTriggerType_success() { // 准备参数 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java index b1e095ea3b..bee6e072e4 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java @@ -1,14 +1,17 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static org.junit.jupiter.api.Assertions.*; @@ -18,11 +21,24 @@ import static org.junit.jupiter.api.Assertions.*; * * @author HUIHUI */ -public class DeviceStateUpdateTriggerMatcherTest extends BaseMockitoUnitTest { +@SpringJUnitConfig(DeviceStateUpdateTriggerMatcherTest.TestConfig.class) +public class DeviceStateUpdateTriggerMatcherTest { + + @Configuration + static class TestConfig { + @Bean + public SpringUtil springUtil() { + return new SpringUtil(); + } + } - @InjectMocks private DeviceStateUpdateTriggerMatcher matcher; + @BeforeEach + public void setUp() { + matcher = new DeviceStateUpdateTriggerMatcher(); + } + @Test public void testGetSupportedTriggerType_success() { // 准备参数 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java index 52ed5ec3de..f332f3097b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java @@ -1,11 +1,11 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; @@ -16,11 +16,15 @@ import static org.junit.jupiter.api.Assertions.*; * * @author HUIHUI */ -public class TimerTriggerMatcherTest extends BaseMockitoUnitTest { +public class TimerTriggerMatcherTest extends BaseMatcherTest { - @InjectMocks private TimerTriggerMatcher matcher; + @BeforeEach + public void setUp() { + matcher = new TimerTriggerMatcher(); + } + @Test public void testGetSupportedTriggerType_success() { // 准备参数 From fe0c1bbf34a3bdc0ed2d3628abdac4b93c23b80f Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 1 Sep 2025 16:26:51 +0800 Subject: [PATCH 02/11] =?UTF-8?q?refactor:=20=E3=80=90IoT=20=E7=89=A9?= =?UTF-8?q?=E8=81=94=E7=BD=91=E3=80=91=E4=BF=AE=E5=A4=8D=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E6=9D=A1=E4=BB=B6=E5=8C=B9=E9=85=8D=E5=99=A8?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E9=97=AE=E9=A2=98=E5=B9=B6=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E5=80=BC=E6=8F=90=E5=8F=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DevicePropertyConditionMatcher.java | 8 +- .../DeviceStateConditionMatcher.java | 6 +- .../DevicePropertyPostTriggerMatcher.java | 6 +- .../DeviceStateUpdateTriggerMatcher.java | 12 +- .../DevicePropertyConditionMatcherTest.java | 274 +++++++++++------- .../iot/core/util/IotDeviceMessageUtils.java | 77 +++++ .../core/util/IotDeviceMessageUtilsTest.java | 141 +++++++++ 7 files changed, 406 insertions(+), 118 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtilsTest.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java index 4a8a8ab6f5..d3120a81bc 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; + import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; @@ -7,6 +8,7 @@ import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; import org.springframework.stereotype.Component; + /** * 设备属性条件匹配器 *

@@ -43,10 +45,10 @@ public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatc return false; } - // 2.1. 获取属性值 - Object propertyValue = message.getParams(); + // 2.1. 获取属性值 - 使用工具类方法正确提取属性值 + Object propertyValue = IotDeviceMessageUtils.extractPropertyValue(message, condition.getIdentifier()); if (propertyValue == null) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空或未找到指定属性"); return false; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java index d5bb97a53e..99000fd06b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; @@ -35,8 +36,9 @@ public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher return false; } - // 2.1 获取设备状态值 - Object stateValue = message.getParams(); + // 2.1 获取设备状态值 - 使用工具类方法正确提取状态值 + // 对于设备状态条件,状态值通过 getIdentifier 获取(实际是从 params.state 字段) + String stateValue = IotDeviceMessageUtils.getIdentifier(message); if (stateValue == null) { IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中设备状态值为空"); return false; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java index 6eccdab427..0ee31a951e 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java @@ -52,10 +52,10 @@ public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatc return false; } - // 2.1 获取属性值 - Object propertyValue = message.getParams(); + // 2.1 获取属性值 - 使用工具类方法正确提取属性值 + Object propertyValue = IotDeviceMessageUtils.extractPropertyValue(message, trigger.getIdentifier()); if (propertyValue == null) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空或未找到指定属性"); return false; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java index edd3c4e907..f3a9f44cb0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; @@ -43,16 +44,17 @@ public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatch return false; } - // 2.1 获取设备状态值 - Object stateValue = message.getParams(); - if (stateValue == null) { + // 2.1 获取设备状态值 - 使用工具类方法正确提取状态值 + // 对于状态更新消息,状态值通过 getIdentifier 获取(实际是从 params.state 字段) + String stateIdentifier = IotDeviceMessageUtils.getIdentifier(message); + if (stateIdentifier == null) { IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); return false; } // 2.2 使用条件评估器进行匹配 - // TODO @puhui999: 状态匹配重新实现 - boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); + // 状态值通常是字符串或数字,直接使用标识符作为状态值 + boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateIdentifier, trigger.getOperator(), trigger.getValue()); if (matched) { IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); } else { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java index 9c34d86216..ddfebc66be 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; -import cn.hutool.core.map.MapUtil; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; @@ -13,7 +12,6 @@ import java.util.HashMap; import java.util.Map; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static org.junit.jupiter.api.Assertions.*; /** @@ -45,27 +43,17 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { int result = matcher.getPriority(); // 断言 - assertEquals(20, result); - } - - @Test - public void testIsEnabled() { - // 调用 - boolean result = matcher.isEnabled(); - - // 断言 - assertTrue(result); + assertEquals(25, result); // 修正:实际返回值是 25 } @Test public void testMatches_temperatureEquals_success() { - // 准备参数 - String propertyName = "temperature"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "temperature"; Double propertyValue = 25.5; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), String.valueOf(propertyValue) ); @@ -79,14 +67,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_humidityGreaterThan_success() { - // 准备参数 - String propertyName = "humidity"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "humidity"; Integer propertyValue = 75; Integer compareValue = 70; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), String.valueOf(compareValue) ); @@ -100,14 +87,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_pressureLessThan_success() { - // 准备参数 - String propertyName = "pressure"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "pressure"; Double propertyValue = 1010.5; Integer compareValue = 1020; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), String.valueOf(compareValue) ); @@ -121,14 +107,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_statusNotEquals_success() { - // 准备参数 - String propertyName = "status"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "status"; String propertyValue = "active"; String compareValue = "inactive"; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), compareValue ); @@ -142,14 +127,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_propertyMismatch_fail() { - // 准备参数 - String propertyName = "temperature"; + // 准备参数:创建属性上报消息,值不满足条件 + String propertyIdentifier = "temperature"; Double propertyValue = 15.0; Integer compareValue = 20; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), String.valueOf(compareValue) ); @@ -162,14 +146,16 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { } @Test - public void testMatches_propertyNotFound_fail() { - // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); + public void testMatches_identifierMismatch_fail() { + // 准备参数:标识符不匹配 + String messageIdentifier = "temperature"; + String conditionIdentifier = "humidity"; + Double propertyValue = 25.5; + IotDeviceMessage message = createPropertyPostMessage(messageIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - randomString(), // 随机不存在的属性名 - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - "50" + conditionIdentifier, + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + String.valueOf(propertyValue) ); // 调用 @@ -182,8 +168,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_nullCondition_fail() { // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); // 调用 boolean result = matcher.matches(message, null); @@ -195,8 +180,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_nullConditionType_fail() { // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); condition.setType(null); @@ -210,8 +194,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_missingIdentifier_fail() { // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); condition.setIdentifier(null); // 缺少标识符 @@ -228,8 +211,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_missingOperator_fail() { // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); condition.setIdentifier("temperature"); @@ -246,8 +228,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_missingParam_fail() { // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); condition.setIdentifier("temperature"); @@ -279,7 +260,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_nullDeviceProperties_fail() { - // 准备参数 + // 准备参数:消息的 params 为 null IotDeviceMessage message = new IotDeviceMessage(); message.setParams(null); IotSceneRuleDO.TriggerCondition condition = createValidCondition( @@ -296,14 +277,79 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { } @Test - public void testMatches_voltageGreaterThanOrEquals_success() { - // 准备参数 - String propertyName = "voltage"; - Double propertyValue = 12.0; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + public void testMatches_propertiesStructure_success() { + // 测试使用 properties 结构的消息(真实的属性上报场景) + String identifier = "temperature"; + Double propertyValue = 25.5; + IotDeviceMessage message = createPropertyPostMessageWithProperties(identifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + identifier, + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // 调用 + boolean result = matcher.matches(message, condition); + + // 断言:修复后的实现应该能正确从 properties 中提取属性值 + assertTrue(result); + } + + @Test + public void testMatches_simpleValueMessage_success() { + // 测试简单值消息(params 直接是属性值) + Double propertyValue = 25.5; + IotDeviceMessage message = createSimpleValueMessage(propertyValue); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "any", // 对于简单值消息,标识符匹配会被跳过 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // 调用 + boolean result = matcher.matches(message, condition); + + // 断言:修复后的实现应该能处理简单值消息 + // 但由于标识符匹配失败,结果为 false + assertFalse(result); + } + + @Test + public void testMatches_valueFieldStructure_success() { + // 测试使用 value 字段的消息结构 + String identifier = "temperature"; + Double propertyValue = 25.5; + + IotDeviceMessage message = new IotDeviceMessage(); + message.setDeviceId(randomLongId()); + message.setMethod("thing.event.post"); + + Map params = new HashMap<>(); + params.put("identifier", identifier); + params.put("value", propertyValue); + message.setParams(params); + + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + identifier, + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // 调用 + boolean result = matcher.matches(message, condition); + + // 断言:修复后的实现应该能从 value 字段提取属性值 + assertTrue(result); + } + + @Test + public void testMatches_voltageGreaterThanOrEquals_success() { + // 准备参数:创建属性上报消息 + String propertyIdentifier = "voltage"; + Double propertyValue = 12.0; + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + propertyIdentifier, IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(), String.valueOf(propertyValue) ); @@ -317,14 +363,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_currentLessThanOrEquals_success() { - // 准备参数 - String propertyName = "current"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "current"; Double propertyValue = 2.5; Double compareValue = 3.0; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(), String.valueOf(compareValue) ); @@ -338,13 +383,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_stringProperty_success() { - // 准备参数 - String propertyName = "mode"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "mode"; String propertyValue = "auto"; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), propertyValue ); @@ -358,13 +402,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_booleanProperty_success() { - // 准备参数 - String propertyName = "enabled"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "enabled"; Boolean propertyValue = true; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), String.valueOf(propertyValue) ); @@ -376,40 +419,61 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { assertTrue(result); } - @Test - public void testMatches_multipleProperties_success() { - // 准备参数 - Map properties = MapUtil.builder(new HashMap()) - .put("temperature", 25.5) - .put("humidity", 60) - .put("status", "active") - .put("enabled", true) - .build(); - IotDeviceMessage message = createDeviceMessage(properties); - String targetProperty = "humidity"; - Integer targetValue = 60; - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - targetProperty, - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - String.valueOf(targetValue) - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - // ========== 辅助方法 ========== /** - * 创建设备消息 + * 创建设备消息用于测试 + * + * 支持的消息格式: + * 1. 直接属性值:params 直接是属性值(适用于简单消息) + * 2. 标识符+值:params 包含 identifier 和对应的属性值 + * 3. properties 结构:params.properties[identifier] = value + * 4. data 结构:params.data[identifier] = value + * 5. value 字段:params.value = value */ - private IotDeviceMessage createDeviceMessage(Map properties) { + private IotDeviceMessage createPropertyPostMessage(String identifier, Object value) { IotDeviceMessage message = new IotDeviceMessage(); message.setDeviceId(randomLongId()); - message.setParams(properties); + message.setMethod("thing.event.post"); // 使用事件上报方法 + + // 创建符合修复后逻辑的 params 结构 + Map params = new HashMap<>(); + params.put("identifier", identifier); + // 直接将属性值放在标识符对应的字段中 + params.put(identifier, value); + message.setParams(params); + + return message; + } + + /** + * 创建使用 properties 结构的消息(模拟真实的属性上报消息) + */ + private IotDeviceMessage createPropertyPostMessageWithProperties(String identifier, Object value) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setDeviceId(randomLongId()); + message.setMethod("thing.property.post"); // 属性上报方法 + + Map properties = new HashMap<>(); + properties.put(identifier, value); + + Map params = new HashMap<>(); + params.put("properties", properties); + message.setParams(params); + + return message; + } + + /** + * 创建简单值消息(params 直接是属性值) + */ + private IotDeviceMessage createSimpleValueMessage(Object value) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setDeviceId(randomLongId()); + message.setMethod("thing.property.post"); + // 直接将属性值作为 params + message.setParams(value); + return message; } diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtils.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtils.java index 5b7778ea0c..65165425c8 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtils.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtils.java @@ -69,6 +69,83 @@ public class IotDeviceMessageUtils { return null; } + /** + * 从设备消息中提取指定标识符的属性值 + * - 支持多种消息格式和属性值提取策略 + * - 兼容现有的消息结构 + * - 提供统一的属性值提取接口 + *

+ * 支持的提取策略(按优先级顺序): + * 1. 直接值:如果 params 不是 Map,直接返回该值(适用于简单消息) + * 2. 标识符字段:从 params[identifier] 获取 + * 3. properties 结构:从 params.properties[identifier] 获取(标准属性上报) + * 4. data 结构:从 params.data[identifier] 获取 + * 5. value 字段:从 params.value 获取(单值消息) + * 6. 单值 Map:如果 Map 只包含 identifier 和一个值,返回该值 + * + * @param message 设备消息 + * @param identifier 属性标识符 + * @return 属性值,如果未找到则返回 null + */ + @SuppressWarnings("unchecked") + public static Object extractPropertyValue(IotDeviceMessage message, String identifier) { + Object params = message.getParams(); + if (params == null) { + return null; + } + + // 策略1:如果 params 不是 Map,直接返回该值(适用于简单的单属性消息) + if (!(params instanceof Map)) { + return params; + } + + Map paramsMap = (Map) params; + + // 策略2:直接通过标识符获取属性值 + Object directValue = paramsMap.get(identifier); + if (directValue != null) { + return directValue; + } + + // 策略3:从 properties 字段中获取(适用于标准属性上报消息) + Object properties = paramsMap.get("properties"); + if (properties instanceof Map) { + Map propertiesMap = (Map) properties; + Object propertyValue = propertiesMap.get(identifier); + if (propertyValue != null) { + return propertyValue; + } + } + + // 策略4:从 data 字段中获取(适用于某些消息格式) + Object data = paramsMap.get("data"); + if (data instanceof Map) { + Map dataMap = (Map) data; + Object dataValue = dataMap.get(identifier); + if (dataValue != null) { + return dataValue; + } + } + + // 策略5:从 value 字段中获取(适用于单值消息) + Object value = paramsMap.get("value"); + if (value != null) { + return value; + } + + // 策略6:如果 Map 只有两个字段且包含 identifier,返回另一个字段的值 + if (paramsMap.size() == 2 && paramsMap.containsKey("identifier")) { + for (Map.Entry entry : paramsMap.entrySet()) { + if (!"identifier".equals(entry.getKey())) { + return entry.getValue(); + } + } + } + + // 未找到对应的属性值 + return null; + } + // ========== Topic 相关 ========== public static String buildMessageBusGatewayDeviceMessageTopic(String serverId) { diff --git a/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtilsTest.java b/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtilsTest.java new file mode 100644 index 0000000000..a6d669d170 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtilsTest.java @@ -0,0 +1,141 @@ +package cn.iocoder.yudao.module.iot.core.util; + +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * {@link IotDeviceMessageUtils} 的单元测试 + * + * @author HUIHUI + */ +public class IotDeviceMessageUtilsTest { + + @Test + public void testExtractPropertyValue_directValue() { + // 测试直接值(非 Map) + IotDeviceMessage message = new IotDeviceMessage(); + message.setParams(25.5); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); + } + + @Test + public void testExtractPropertyValue_directIdentifier() { + // 测试直接通过标识符获取 + IotDeviceMessage message = new IotDeviceMessage(); + Map params = new HashMap<>(); + params.put("temperature", 25.5); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); + } + + @Test + public void testExtractPropertyValue_propertiesStructure() { + // 测试 properties 结构 + IotDeviceMessage message = new IotDeviceMessage(); + Map properties = new HashMap<>(); + properties.put("temperature", 25.5); + properties.put("humidity", 60); + + Map params = new HashMap<>(); + params.put("properties", properties); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); + } + + @Test + public void testExtractPropertyValue_dataStructure() { + // 测试 data 结构 + IotDeviceMessage message = new IotDeviceMessage(); + Map data = new HashMap<>(); + data.put("temperature", 25.5); + + Map params = new HashMap<>(); + params.put("data", data); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); + } + + @Test + public void testExtractPropertyValue_valueField() { + // 测试 value 字段 + IotDeviceMessage message = new IotDeviceMessage(); + Map params = new HashMap<>(); + params.put("identifier", "temperature"); + params.put("value", 25.5); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); + } + + @Test + public void testExtractPropertyValue_singleValueMap() { + // 测试单值 Map(包含 identifier 和一个值) + IotDeviceMessage message = new IotDeviceMessage(); + Map params = new HashMap<>(); + params.put("identifier", "temperature"); + params.put("actualValue", 25.5); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); + } + + @Test + public void testExtractPropertyValue_notFound() { + // 测试未找到属性值 + IotDeviceMessage message = new IotDeviceMessage(); + Map params = new HashMap<>(); + params.put("humidity", 60); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertNull(result); + } + + @Test + public void testExtractPropertyValue_nullParams() { + // 测试 params 为 null + IotDeviceMessage message = new IotDeviceMessage(); + message.setParams(null); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertNull(result); + } + + @Test + public void testExtractPropertyValue_priorityOrder() { + // 测试优先级顺序:直接标识符 > properties > data > value + IotDeviceMessage message = new IotDeviceMessage(); + + Map properties = new HashMap<>(); + properties.put("temperature", 20.0); + + Map data = new HashMap<>(); + data.put("temperature", 30.0); + + Map params = new HashMap<>(); + params.put("temperature", 25.5); // 最高优先级 + params.put("properties", properties); + params.put("data", data); + params.put("value", 40.0); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); // 应该返回直接标识符的值 + } +} From bc0292eb6122cf3b47141399fb85d4203e0f4c94 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 1 Sep 2025 17:20:15 +0800 Subject: [PATCH 03/11] =?UTF-8?q?feat:=20=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E9=87=8D=E6=9E=84=E8=AE=BE=E5=A4=87=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E8=AE=BE=E7=BD=AE=E6=89=A7=E8=A1=8C=E5=99=A8=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8D=95=E8=AE=BE=E5=A4=87=E5=92=8C=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E8=AE=BE=E5=A4=87=E6=93=8D=E4=BD=9C=E3=80=82=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E8=AE=BE=E5=A4=87=E5=B1=9E=E6=80=A7=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E5=99=A8=EF=BC=8C=E6=94=AF=E6=8C=81=E5=8D=95?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=92=8C=E6=89=B9=E9=87=8F=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IotDeviceControlSceneRuleAction.java | 127 ++++++++++++--- ...IotDeviceServiceInvokeSceneRuleAction.java | 145 ++++++++++++++++++ 2 files changed, 252 insertions(+), 20 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceServiceInvokeSceneRuleAction.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java index b71a92091b..dd10ed8134 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java @@ -1,6 +1,10 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.action; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum; import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; @@ -9,8 +13,11 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import java.util.List; +import java.util.Map; + /** - * IoT 设备控制的 {@link IotSceneRuleAction} 实现类 + * IoT 设备属性设置的 {@link IotSceneRuleAction} 实现类 * * @author 芋道源码 */ @@ -23,28 +30,108 @@ public class IotDeviceControlSceneRuleAction implements IotSceneRuleAction { @Resource private IotDeviceMessageService deviceMessageService; - // TODO @puhui999:这里 @Override public void execute(IotDeviceMessage message, IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { - //IotSceneRuleDO.ActionDeviceControl control = actionConfig.getDeviceControl(); - //Assert.notNull(control, "设备控制配置不能为空"); - //// 遍历每个设备,下发消息 - //control.getDeviceNames().forEach(deviceName -> { - // IotDeviceDO device = deviceService.getDeviceFromCache(control.getProductKey(), deviceName); - // if (device == null) { - // log.error("[execute][message({}) actionConfig({}) 对应的设备不存在]", message, actionConfig); - // return; - // } - // try { - // // TODO @芋艿:@puhui999:这块可能要改,从 type => method - // IotDeviceMessage downstreamMessage = deviceMessageService.sendDeviceMessage(IotDeviceMessage.requestOf( - // control.getType() + control.getIdentifier(), control.getData()).setDeviceId(device.getId())); - // log.info("[execute][message({}) actionConfig({}) 下发消息({})成功]", message, actionConfig, downstreamMessage); - // } catch (Exception e) { - // log.error("[execute][message({}) actionConfig({}) 下发消息失败]", message, actionConfig, e); - // } - //}); + // 1. 参数校验 + if (actionConfig.getDeviceId() == null) { + log.error("[execute][规则场景({}) 动作配置({}) 设备编号不能为空]", rule.getId(), actionConfig); + return; + } + if (StrUtil.isEmpty(actionConfig.getIdentifier())) { + log.error("[execute][规则场景({}) 动作配置({}) 属性标识符不能为空]", rule.getId(), actionConfig); + return; + } + + // 2. 判断是否为全部设备 + if (IotDeviceDO.DEVICE_ID_ALL.equals(actionConfig.getDeviceId())) { + executeForAllDevices(message, rule, actionConfig); + } else { + executeForSingleDevice(message, rule, actionConfig); + } + } + + /** + * 为单个设备执行属性设置 + */ + private void executeForSingleDevice(IotDeviceMessage message, + IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { + // 1. 获取设备信息 + IotDeviceDO device = deviceService.getDeviceFromCache(actionConfig.getDeviceId()); + if (device == null) { + log.error("[executeForSingleDevice][规则场景({}) 动作配置({}) 对应的设备({}) 不存在]", + rule.getId(), actionConfig, actionConfig.getDeviceId()); + return; + } + + // 2. 执行属性设置 + executePropertySetForDevice(rule, actionConfig, device); + } + + /** + * 为产品下的所有设备执行属性设置 + */ + private void executeForAllDevices(IotDeviceMessage message, + IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { + // 1. 参数校验 + if (actionConfig.getProductId() == null) { + log.error("[executeForAllDevices][规则场景({}) 动作配置({}) 产品编号不能为空]", rule.getId(), actionConfig); + return; + } + + // 2. 获取产品下的所有设备 + List devices = deviceService.getDeviceListByProductId(actionConfig.getProductId()); + if (CollUtil.isEmpty(devices)) { + log.warn("[executeForAllDevices][规则场景({}) 动作配置({}) 产品({}) 下没有设备]", + rule.getId(), actionConfig, actionConfig.getProductId()); + return; + } + + // 3. 遍历所有设备执行属性设置 + for (IotDeviceDO device : devices) { + executePropertySetForDevice(rule, actionConfig, device); + } + } + + /** + * 为指定设备执行属性设置 + */ + private void executePropertySetForDevice(IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig, IotDeviceDO device) { + // 1. 构建属性设置消息 + IotDeviceMessage downstreamMessage = buildPropertySetMessage(actionConfig, device); + if (downstreamMessage == null) { + log.error("[executePropertySetForDevice][规则场景({}) 动作配置({}) 设备({}) 构建属性设置消息失败]", + rule.getId(), actionConfig, device.getId()); + return; + } + + // 2. 发送设备消息 + try { + IotDeviceMessage result = deviceMessageService.sendDeviceMessage(downstreamMessage, device); + log.info("[executePropertySetForDevice][规则场景({}) 动作配置({}) 设备({}) 属性设置消息({}) 发送成功]", + rule.getId(), actionConfig, device.getId(), result.getId()); + } catch (Exception e) { + log.error("[executePropertySetForDevice][规则场景({}) 动作配置({}) 设备({}) 属性设置消息发送失败]", + rule.getId(), actionConfig, device.getId(), e); + } + } + + /** + * 构建属性设置消息 + * + * @param actionConfig 动作配置 + * @param device 设备信息 + * @return 设备消息 + */ + private IotDeviceMessage buildPropertySetMessage(IotSceneRuleDO.Action actionConfig, IotDeviceDO device) { + try { + // 属性设置参数格式: {"properties": {"identifier": value}} + Object params = Map.of("properties", Map.of(actionConfig.getIdentifier(), actionConfig.getParams())); + return IotDeviceMessage.requestOf(IotDeviceMessageMethodEnum.PROPERTY_SET.getMethod(), params); + } catch (Exception e) { + log.error("[buildPropertySetMessage][构建属性设置消息异常]", e); + return null; + } } @Override diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceServiceInvokeSceneRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceServiceInvokeSceneRuleAction.java new file mode 100644 index 0000000000..eb7bedf2f0 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceServiceInvokeSceneRuleAction.java @@ -0,0 +1,145 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.action; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum; +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; +import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +/** + * IoT 设备服务调用的 {@link IotSceneRuleAction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class IotDeviceServiceInvokeSceneRuleAction implements IotSceneRuleAction { + + @Resource + private IotDeviceService deviceService; + @Resource + private IotDeviceMessageService deviceMessageService; + + @Override + public void execute(IotDeviceMessage message, + IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { + // 1. 参数校验 + if (actionConfig.getDeviceId() == null) { + log.error("[execute][规则场景({}) 动作配置({}) 设备编号不能为空]", rule.getId(), actionConfig); + return; + } + if (StrUtil.isEmpty(actionConfig.getIdentifier())) { + log.error("[execute][规则场景({}) 动作配置({}) 服务标识符不能为空]", rule.getId(), actionConfig); + return; + } + + // 2. 判断是否为全部设备 + if (IotDeviceDO.DEVICE_ID_ALL.equals(actionConfig.getDeviceId())) { + executeForAllDevices(message, rule, actionConfig); + } else { + executeForSingleDevice(message, rule, actionConfig); + } + } + + /** + * 为单个设备执行服务调用 + */ + private void executeForSingleDevice(IotDeviceMessage message, + IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { + // 1. 获取设备信息 + IotDeviceDO device = deviceService.getDeviceFromCache(actionConfig.getDeviceId()); + if (device == null) { + log.error("[executeForSingleDevice][规则场景({}) 动作配置({}) 对应的设备({}) 不存在]", + rule.getId(), actionConfig, actionConfig.getDeviceId()); + return; + } + + // 2. 执行服务调用 + executeServiceInvokeForDevice(rule, actionConfig, device); + } + + /** + * 为产品下的所有设备执行服务调用 + */ + private void executeForAllDevices(IotDeviceMessage message, + IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { + // 1. 参数校验 + if (actionConfig.getProductId() == null) { + log.error("[executeForAllDevices][规则场景({}) 动作配置({}) 产品编号不能为空]", rule.getId(), actionConfig); + return; + } + + // 2. 获取产品下的所有设备 + List devices = deviceService.getDeviceListByProductId(actionConfig.getProductId()); + if (CollUtil.isEmpty(devices)) { + log.warn("[executeForAllDevices][规则场景({}) 动作配置({}) 产品({}) 下没有设备]", + rule.getId(), actionConfig, actionConfig.getProductId()); + return; + } + + // 3. 遍历所有设备执行服务调用 + for (IotDeviceDO device : devices) { + executeServiceInvokeForDevice(rule, actionConfig, device); + } + } + + /** + * 为指定设备执行服务调用 + */ + private void executeServiceInvokeForDevice(IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig, IotDeviceDO device) { + // 1. 构建服务调用消息 + IotDeviceMessage downstreamMessage = buildServiceInvokeMessage(actionConfig, device); + if (downstreamMessage == null) { + log.error("[executeServiceInvokeForDevice][规则场景({}) 动作配置({}) 设备({}) 构建服务调用消息失败]", + rule.getId(), actionConfig, device.getId()); + return; + } + + // 2. 发送设备消息 + try { + IotDeviceMessage result = deviceMessageService.sendDeviceMessage(downstreamMessage, device); + log.info("[executeServiceInvokeForDevice][规则场景({}) 动作配置({}) 设备({}) 服务调用消息({}) 发送成功]", + rule.getId(), actionConfig, device.getId(), result.getId()); + } catch (Exception e) { + log.error("[executeServiceInvokeForDevice][规则场景({}) 动作配置({}) 设备({}) 服务调用消息发送失败]", + rule.getId(), actionConfig, device.getId(), e); + } + } + + /** + * 构建服务调用消息 + * + * @param actionConfig 动作配置 + * @param device 设备信息 + * @return 设备消息 + */ + private IotDeviceMessage buildServiceInvokeMessage(IotSceneRuleDO.Action actionConfig, IotDeviceDO device) { + try { + // 服务调用参数格式: {"identifier": "serviceId", "params": {...}} + Object params = Map.of( + "identifier", actionConfig.getIdentifier(), + "params", actionConfig.getParams() != null ? actionConfig.getParams() : Map.of() + ); + return IotDeviceMessage.requestOf(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(), params); + } catch (Exception e) { + log.error("[buildServiceInvokeMessage][构建服务调用消息异常]", e); + return null; + } + } + + @Override + public IotSceneRuleActionTypeEnum getType() { + return IotSceneRuleActionTypeEnum.DEVICE_SERVICE_INVOKE; + } + +} From 1cdae71d9484d6827d1e06432e5d98d1e92258a7 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 1 Sep 2025 17:48:20 +0800 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E5=AE=9E=E7=8E=B0=E5=9C=BA=E6=99=AF=E8=A7=84?= =?UTF-8?q?=E5=88=99=E5=AE=9A=E6=97=B6=E8=A7=A6=E5=8F=91=E5=99=A8=E7=9A=84?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=EF=BC=8C=E6=96=B0=E5=A2=9E=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E8=A7=A6=E5=8F=91=E5=99=A8=E5=A4=84=E7=90=86=E5=99=A8=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=9C=BA=E6=99=AF=E8=A7=84=E5=88=99=E7=9A=84?= =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E6=B3=A8=E5=86=8C=E3=80=81?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E3=80=81=E5=88=A0=E9=99=A4=E7=AD=89=E7=94=9F?= =?UTF-8?q?=E5=91=BD=E5=91=A8=E6=9C=9F=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rule/scene/IotSceneRuleServiceImpl.java | 28 ++- .../scene/timer/IotSceneRuleTimerHandler.java | 178 ++++++++++++++++++ 2 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandler.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java index 7cbc5b56be..ee7351f717 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java @@ -17,11 +17,11 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; import cn.iocoder.yudao.module.iot.service.product.IotProductService; import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotSceneRuleAction; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager; +import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotSceneRuleTimerHandler; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -47,9 +47,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @Resource private IotSceneRuleMapper sceneRuleMapper; - // TODO @puhui999:定时任务,基于它调度; - @Resource(name = "iotSchedulerManager") - private IotSchedulerManager schedulerManager; @Resource private IotProductService productService; @Resource @@ -59,11 +56,17 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { private IotSceneRuleMatcherManager sceneRuleMatcherManager; @Resource private List sceneRuleActions; + @Resource + private IotSceneRuleTimerHandler timerHandler; @Override public Long createSceneRule(IotSceneRuleSaveReqVO createReqVO) { IotSceneRuleDO sceneRule = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class); sceneRuleMapper.insert(sceneRule); + + // 注册定时触发器 + timerHandler.registerTimerTriggers(sceneRule); + return sceneRule.getId(); } @@ -74,6 +77,9 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { // 更新 IotSceneRuleDO updateObj = BeanUtils.toBean(updateReqVO, IotSceneRuleDO.class); sceneRuleMapper.updateById(updateObj); + + // 更新定时触发器 + timerHandler.updateTimerTriggers(updateObj); } @Override @@ -83,12 +89,26 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { // 更新状态 IotSceneRuleDO updateObj = new IotSceneRuleDO().setId(id).setStatus(status); sceneRuleMapper.updateById(updateObj); + + // 根据状态管理定时触发器 + if (CommonStatusEnum.isEnable(status)) { + // 启用时,获取完整的场景规则信息并注册定时触发器 + IotSceneRuleDO sceneRule = sceneRuleMapper.selectById(id); + if (sceneRule != null) { + timerHandler.registerTimerTriggers(sceneRule); + } + } else { + // 禁用时,暂停定时触发器 + timerHandler.pauseTimerTriggers(id); + } } @Override public void deleteSceneRule(Long id) { // 校验存在 validateSceneRuleExists(id); + // 删除定时触发器 + timerHandler.unregisterTimerTriggers(id); // 删除 sceneRuleMapper.deleteById(id); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandler.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandler.java new file mode 100644 index 0000000000..ddb6367506 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandler.java @@ -0,0 +1,178 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.timer; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; +import cn.iocoder.yudao.module.iot.job.rule.IotSceneRuleJob; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.quartz.SchedulerException; +import org.springframework.stereotype.Component; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; + +/** + * IoT 场景规则定时触发器处理器 + *

+ * 负责管理定时触发器的注册、更新、删除等操作 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class IotSceneRuleTimerHandler { + + @Resource(name = "iotSchedulerManager") + private IotSchedulerManager schedulerManager; + + /** + * 注册场景规则的定时触发器 + * + * @param sceneRule 场景规则 + */ + public void registerTimerTriggers(IotSceneRuleDO sceneRule) { + if (sceneRule == null || CollUtil.isEmpty(sceneRule.getTriggers())) { + return; + } + + // 过滤出定时触发器 + List timerTriggers = filterList(sceneRule.getTriggers(), + trigger -> ObjUtil.equals(trigger.getType(), IotSceneRuleTriggerTypeEnum.TIMER.getType())); + + if (CollUtil.isEmpty(timerTriggers)) { + return; + } + + // 注册每个定时触发器 + timerTriggers.forEach(trigger -> registerSingleTimerTrigger(sceneRule, trigger)); + } + + /** + * 更新场景规则的定时触发器 + * + * @param sceneRule 场景规则 + */ + public void updateTimerTriggers(IotSceneRuleDO sceneRule) { + if (sceneRule == null) { + return; + } + + // 先删除旧的定时任务 + unregisterTimerTriggers(sceneRule.getId()); + + // 如果场景规则已禁用,则不重新注册 + if (CommonStatusEnum.isDisable(sceneRule.getStatus())) { + log.info("[updateTimerTriggers][场景规则({}) 已禁用,不注册定时触发器]", sceneRule.getId()); + return; + } + + // 重新注册定时触发器 + registerTimerTriggers(sceneRule); + } + + /** + * 注销场景规则的定时触发器 + * + * @param sceneRuleId 场景规则ID + */ + public void unregisterTimerTriggers(Long sceneRuleId) { + if (sceneRuleId == null) { + return; + } + + String jobName = buildJobName(sceneRuleId); + try { + schedulerManager.deleteJob(jobName); + log.info("[unregisterTimerTriggers][场景规则({}) 定时触发器注销成功]", sceneRuleId); + } catch (SchedulerException e) { + log.error("[unregisterTimerTriggers][场景规则({}) 定时触发器注销失败]", sceneRuleId, e); + } + } + + /** + * 暂停场景规则的定时触发器 + * + * @param sceneRuleId 场景规则ID + */ + public void pauseTimerTriggers(Long sceneRuleId) { + if (sceneRuleId == null) { + return; + } + + String jobName = buildJobName(sceneRuleId); + try { + schedulerManager.pauseJob(jobName); + log.info("[pauseTimerTriggers][场景规则({}) 定时触发器暂停成功]", sceneRuleId); + } catch (SchedulerException e) { + log.error("[pauseTimerTriggers][场景规则({}) 定时触发器暂停失败]", sceneRuleId, e); + } + } + + /** + * 恢复场景规则的定时触发器 + * + * @param sceneRuleId 场景规则ID + */ + public void resumeTimerTriggers(Long sceneRuleId) { + if (sceneRuleId == null) { + return; + } + + String jobName = buildJobName(sceneRuleId); + try { + schedulerManager.resumeJob(jobName); + log.info("[resumeTimerTriggers][场景规则({}) 定时触发器恢复成功]", sceneRuleId); + } catch (SchedulerException e) { + log.error("[resumeTimerTriggers][场景规则({}) 定时触发器恢复失败]", sceneRuleId, e); + } + } + + /** + * 注册单个定时触发器 + * + * @param sceneRule 场景规则 + * @param trigger 定时触发器配置 + */ + private void registerSingleTimerTrigger(IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) { + // 1. 参数校验 + if (StrUtil.isBlank(trigger.getCronExpression())) { + log.error("[registerSingleTimerTrigger][场景规则({}) 定时触发器缺少 CRON 表达式]", sceneRule.getId()); + return; + } + + // 2. 构建任务名称和数据 + String jobName = buildJobName(sceneRule.getId()); + + try { + // 3. 注册定时任务 + schedulerManager.addOrUpdateJob( + IotSceneRuleJob.class, + jobName, + trigger.getCronExpression(), + IotSceneRuleJob.buildJobDataMap(sceneRule.getId()) + ); + + log.info("[registerSingleTimerTrigger][场景规则({}) 定时触发器注册成功,CRON: {}]", + sceneRule.getId(), trigger.getCronExpression()); + } catch (SchedulerException e) { + log.error("[registerSingleTimerTrigger][场景规则({}) 定时触发器注册失败,CRON: {}]", + sceneRule.getId(), trigger.getCronExpression(), e); + } + } + + /** + * 构建任务名称 + * + * @param sceneRuleId 场景规则ID + * @return 任务名称 + */ + private String buildJobName(Long sceneRuleId) { + return "iot_scene_rule_timer_" + sceneRuleId; + } +} From e639dbfeb7bc8963fd34014e40138383ae63a3a6 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 3 Sep 2025 23:24:22 +0800 Subject: [PATCH 05/11] =?UTF-8?q?review=EF=BC=9A=E3=80=90iot=20=E7=89=A9?= =?UTF-8?q?=E8=81=94=E7=BD=91=E3=80=91=E5=9C=BA=E6=99=AF=E8=81=94=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rule/IotSceneRuleTriggerTypeEnum.java | 1 - .../rule/scene/IotSceneRuleServiceImpl.java | 57 ++++++++++--------- .../IotAlertRecoverSceneRuleAction.java | 1 - .../IotAlertTriggerSceneRuleAction.java | 1 - .../scene/matcher/IotSceneRuleMatcher.java | 6 +- .../matcher/IotSceneRuleMatcherHelper.java | 6 +- .../matcher/IotSceneRuleMatcherManager.java | 19 +++---- .../CurrentTimeConditionMatcher.java | 5 +- .../DevicePropertyConditionMatcher.java | 4 +- .../DeviceStateConditionMatcher.java | 4 +- .../IotSceneRuleConditionMatcher.java | 9 +-- .../DeviceEventPostTriggerMatcher.java | 4 +- .../DevicePropertyPostTriggerMatcher.java | 4 +- .../DeviceServiceInvokeTriggerMatcher.java | 4 +- .../DeviceStateUpdateTriggerMatcher.java | 4 +- .../trigger/IotSceneRuleTriggerMatcher.java | 9 +-- .../matcher/trigger/TimerTriggerMatcher.java | 5 +- .../scene/timer/IotSceneRuleTimerHandler.java | 50 +++++----------- .../rule/scene/matcher/BaseMatcherTest.java | 1 + 19 files changed, 71 insertions(+), 123 deletions(-) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java index bfc84c9f60..fac8ba6b80 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java @@ -19,7 +19,6 @@ import java.util.Arrays; @Getter public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable { - // TODO @芋艿:后续“对应”部分,要 @下,等包结构梳理完; /** * 设备上下线变更 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java index ee7351f717..c631e34586 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java @@ -84,13 +84,14 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @Override public void updateSceneRuleStatus(Long id, Integer status) { - // 校验存在 + // 1. 校验存在 validateSceneRuleExists(id); - // 更新状态 + + // 2. 更新状态 IotSceneRuleDO updateObj = new IotSceneRuleDO().setId(id).setStatus(status); sceneRuleMapper.updateById(updateObj); - // 根据状态管理定时触发器 + // 3. 根据状态管理定时触发器 if (CommonStatusEnum.isEnable(status)) { // 启用时,获取完整的场景规则信息并注册定时触发器 IotSceneRuleDO sceneRule = sceneRuleMapper.selectById(id); @@ -105,12 +106,14 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @Override public void deleteSceneRule(Long id) { - // 校验存在 + // 1. 校验存在 validateSceneRuleExists(id); - // 删除定时触发器 - timerHandler.unregisterTimerTriggers(id); - // 删除 + + // 2. 删除 sceneRuleMapper.deleteById(id); + + // 3. 删除定时触发器 + timerHandler.unregisterTimerTriggers(id); } private void validateSceneRuleExists(Long id) { @@ -146,16 +149,17 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { return sceneRuleMapper.selectListByStatus(status); } - // TODO 芋艿,缓存待实现 @puhui999 + // TODO @puhui999:缓存待实现 @Override @TenantIgnore // 忽略租户隔离:因为 IotSceneRuleMessageHandler 调用时,一般未传递租户,所以需要忽略 public List getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId) { + // 1. 查询启用状态的规则场景 + // TODO @puhui999:这里查询 enable 的; List list = sceneRuleMapper.selectList(); - // 只返回启用状态的规则场景 List enabledList = filterList(list, sceneRule -> CommonStatusEnum.isEnable(sceneRule.getStatus())); - // 根据 productKey 和 deviceName 进行匹配 + // 2. 根据 productKey 和 deviceName 进行匹配 return filterList(enabledList, sceneRule -> { if (CollUtil.isEmpty(sceneRule.getTriggers())) { return false; @@ -164,21 +168,19 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { for (IotSceneRuleDO.Trigger trigger : sceneRule.getTriggers()) { // 检查触发器是否匹配指定的产品和设备 try { - // 1. 检查产品是否匹配 - if (trigger.getProductId() == null) { - return false; - } - if (trigger.getDeviceId() == null) { + // 检查产品是否匹配 + if (trigger.getProductId() == null || trigger.getDeviceId() == null) { return false; } // 检查是否是全部设备的特殊标识 if (IotDeviceDO.DEVICE_ID_ALL.equals(trigger.getDeviceId())) { - return true; // 匹配所有设备 + return true; } // 检查具体设备 ID 是否匹配 return ObjUtil.equal(productId, trigger.getProductId()) && ObjUtil.equal(deviceId, trigger.getDeviceId()); } catch (Exception e) { - log.warn("[isMatchProductAndDevice][产品({}) 设备({}) 匹配触发器异常]", productId, deviceId, e); + log.warn("[getSceneRuleListByProductIdAndDeviceIdFromCache][产品({}) 设备({}) 匹配触发器异常]", + productId, deviceId, e); return false; } } @@ -188,7 +190,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @Override public void executeSceneRuleByDevice(IotDeviceMessage message) { - // TODO @芋艿:这里的 tenantId,通过设备获取;@puhui999: + // TODO @puhui999:这里的 tenantId,通过设备获取; TenantUtils.execute(message.getTenantId(), () -> { // 1. 获得设备匹配的规则场景 List sceneRules = getMatchedSceneRuleListByMessage(message); @@ -234,7 +236,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { */ private List getMatchedSceneRuleListByMessage(IotDeviceMessage message) { // 1. 匹配设备 - // TODO @芋艿:可能需要 getSelf(); 缓存 @puhui999; + // TODO 缓存 @puhui999:可能需要 getSelf() // 1.1 通过 deviceId 获取设备信息 IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId()); if (device == null) { @@ -293,7 +295,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { */ private boolean matchSingleTrigger(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) { try { - // 2. 检查触发器的条件分组 return sceneRuleMatcherManager.isMatched(message, trigger) && isTriggerConditionGroupsMatched(message, trigger, sceneRule); } catch (Exception e) { log.error("[matchSingleTrigger][触发器匹配异常] sceneRuleId: {}, triggerType: {}, message: {}", @@ -310,18 +311,19 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { * @param sceneRule 场景规则(用于日志) * @return 是否匹配 */ - private boolean isTriggerConditionGroupsMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) { - // 如果没有条件分组,则认为匹配成功(只依赖基础触发器匹配) + private boolean isTriggerConditionGroupsMatched(IotDeviceMessage message, + IotSceneRuleDO.Trigger trigger, + IotSceneRuleDO sceneRule) { + // 1. 如果没有条件分组,则认为匹配成功(只依赖基础触发器匹配) if (CollUtil.isEmpty(trigger.getConditionGroups())) { return true; } - // 检查条件分组:分组与分组之间是"或"的关系,条件与条件之间是"且"的关系 + // 2. 检查条件分组:分组与分组之间是"或"的关系,条件与条件之间是"且"的关系 for (List conditionGroup : trigger.getConditionGroups()) { if (CollUtil.isEmpty(conditionGroup)) { continue; } - // 检查当前分组中的所有条件是否都匹配(且关系) boolean allConditionsMatched = true; for (IotSceneRuleDO.TriggerCondition condition : conditionGroup) { @@ -330,14 +332,13 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { break; } } - // 如果当前分组的所有条件都匹配,则整个触发器匹配成功 if (allConditionsMatched) { return true; } } - // 所有分组都不匹配 + // 3. 所有分组都不匹配 return false; } @@ -372,13 +373,13 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { sceneRules.forEach(sceneRule -> { // 2. 遍历规则场景的动作 sceneRule.getActions().forEach(actionConfig -> { - // 3.1 获取对应的动作 Action 数组 + // 2.1 获取对应的动作 Action 数组 List actions = filterList(sceneRuleActions, action -> action.getType().getType().equals(actionConfig.getType())); if (CollUtil.isEmpty(actions)) { return; } - // 3.2 执行动作 + // 2.2 执行动作 actions.forEach(action -> { try { action.execute(message, sceneRule, actionConfig); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertRecoverSceneRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertRecoverSceneRuleAction.java index 851f3815fa..c2fe50c93a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertRecoverSceneRuleAction.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertRecoverSceneRuleAction.java @@ -14,7 +14,6 @@ import java.util.List; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -// TODO @puhui999、@芋艿:未测试;需要场景联动开发完 /** * IoT 告警恢复的 {@link IotSceneRuleAction} 实现类 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleAction.java index 28223dbd6e..5ff4a61dd0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleAction.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotAlertTriggerSceneRuleAction.java @@ -17,7 +17,6 @@ import org.springframework.stereotype.Component; import javax.annotation.Nullable; import java.util.List; -// TODO @puhui999、@芋艿:未测试;需要场景联动开发完 /** * IoT 告警触发的 {@link IotSceneRuleAction} 实现类 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java index 84795d9fe5..cf312bea6c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java @@ -4,10 +4,8 @@ import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition.IotScene import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger.IotSceneRuleTriggerMatcher; /** - * IoT 场景规则匹配器基础接口 - *

- * 定义所有匹配器的通用行为,包括优先级、名称和启用状态 - *

+ * IoT 场景规则匹配器基础接口:定义所有匹配器的通用行为,包括优先级、名称和启用状态 + * * - {@link IotSceneRuleTriggerMatcher} 触发器匹配器 * - {@link IotSceneRuleConditionMatcher} 条件匹配器 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java index db111e4472..937add3bbf 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java @@ -18,10 +18,8 @@ import java.util.Map; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; /** - * IoT 场景规则匹配器工具类 - *

- * 提供通用的条件评估逻辑和工具方法,供触发器和条件匹配器使用 - *

+ * IoT 场景规则匹配器工具类:提供通用的条件评估逻辑和工具方法,供触发器和条件匹配器使用 + * * 该类包含了匹配器实现中常用的工具方法,如条件评估、参数校验、日志记录等 * * @author HUIHUI diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java index 3658fc07cd..ddca893c42 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java @@ -16,9 +16,7 @@ import java.util.function.Function; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** - * IoT 场景规则匹配器统一管理器 - *

- * 负责管理所有匹配器(触发器匹配器和条件匹配器),并提供统一的匹配入口 + * IoT 场景规则匹配器统一管理器:负责管理所有匹配器(触发器匹配器和条件匹配器),并提供统一的匹配入口 * * @author HUIHUI */ @@ -44,13 +42,12 @@ public class IotSceneRuleMatcherManager { return; } - // 按优先级排序并过滤启用的匹配器 + // 1.1 按优先级排序并过滤启用的匹配器 List allMatchers = matchers.stream() .filter(IotSceneRuleMatcher::isEnabled) .sorted(Comparator.comparing(IotSceneRuleMatcher::getPriority)) .toList(); - - // 分离触发器匹配器和条件匹配器 + // 1.2 分离触发器匹配器和条件匹配器 List triggerMatchers = allMatchers.stream() .filter(matcher -> matcher instanceof IotSceneRuleTriggerMatcher) .map(matcher -> (IotSceneRuleTriggerMatcher) matcher) @@ -60,7 +57,7 @@ public class IotSceneRuleMatcherManager { .map(matcher -> (IotSceneRuleConditionMatcher) matcher) .toList(); - // 构建触发器匹配器映射表 + // 2.1 构建触发器匹配器映射表 this.triggerMatchers = convertMap(triggerMatchers, IotSceneRuleTriggerMatcher::getSupportedTriggerType, Function.identity(), (existing, replacement) -> { @@ -70,7 +67,7 @@ public class IotSceneRuleMatcherManager { existing.getSupportedTriggerType() : replacement.getSupportedTriggerType()); return existing.getPriority() <= replacement.getPriority() ? existing : replacement; }, LinkedHashMap::new); - // 构建条件匹配器映射表 + // 2.2 构建条件匹配器映射表 this.conditionMatchers = convertMap(conditionMatchers, IotSceneRuleConditionMatcher::getSupportedConditionType, Function.identity(), (existing, replacement) -> { @@ -82,7 +79,7 @@ public class IotSceneRuleMatcherManager { }, LinkedHashMap::new); - // 日志输出初始化信息 + // 3. 日志输出初始化信息 log.info("[IotSceneRuleMatcherManager][初始化完成,共加载({})个匹配器,其中触发器匹配器({})个,条件匹配器({})个]", allMatchers.size(), this.triggerMatchers.size(), this.conditionMatchers.size()); this.triggerMatchers.forEach((type, matcher) -> @@ -135,7 +132,7 @@ public class IotSceneRuleMatcherManager { return false; } - // 根据条件类型查找对应的匹配器 + // 1. 根据条件类型查找对应的匹配器 IotSceneRuleConditionTypeEnum conditionType = IotSceneRuleConditionTypeEnum.typeOf(condition.getType()); if (conditionType == null) { log.warn("[isConditionMatched][conditionType({}) 未知的条件类型]", condition.getType()); @@ -147,7 +144,7 @@ public class IotSceneRuleMatcherManager { return false; } - // 执行匹配逻辑 + // 2. 执行匹配逻辑 try { return matcher.matches(message, condition); } catch (Exception e) { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java index 81c8fba597..cb85f36203 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java @@ -16,10 +16,9 @@ import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.List; +// TODO @puhui999:是不是 IoT 的前缀,都加下哈; /** - * 当前时间条件匹配器 - *

- * 处理时间相关的子条件匹配逻辑 + * 当前时间条件匹配器:处理时间相关的子条件匹配逻辑 * * @author HUIHUI */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java index d3120a81bc..f4316c0b5d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java @@ -10,9 +10,7 @@ import org.springframework.stereotype.Component; /** - * 设备属性条件匹配器 - *

- * 处理设备属性相关的子条件匹配逻辑 + * 设备属性条件匹配器:处理设备属性相关的子条件匹配逻辑 * * @author HUIHUI */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java index 99000fd06b..ceb1ee8c8b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java @@ -8,9 +8,7 @@ import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatche import org.springframework.stereotype.Component; /** - * 设备状态条件匹配器 - *

- * 处理设备状态相关的子条件匹配逻辑 + * 设备状态条件匹配器:处理设备状态相关的子条件匹配逻辑 * * @author HUIHUI */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotSceneRuleConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotSceneRuleConditionMatcher.java index 875e8b1563..c9f720dfcb 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotSceneRuleConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotSceneRuleConditionMatcher.java @@ -6,12 +6,9 @@ import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcher; /** - * IoT 场景规则条件匹配器接口 - *

- * 专门处理子条件的匹配逻辑,如设备状态、属性值、时间条件等 - *

- * 条件匹配器负责判断设备消息是否满足场景规则的附加条件, - * 在触发器匹配成功后进行进一步的条件筛选 + * IoT 场景规则条件匹配器接口:专门处理子条件的匹配逻辑,如设备状态、属性值、时间条件等 + * + * 条件匹配器负责判断设备消息是否满足场景规则的附加条件,在触发器匹配成功后进行进一步的条件筛选 * * @author HUIHUI */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java index 1ab1bb9d26..2a843d1c2c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java @@ -10,9 +10,7 @@ import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatche import org.springframework.stereotype.Component; /** - * 设备事件上报触发器匹配器 - *

- * 处理设备事件上报的触发器匹配逻辑 + * 设备事件上报触发器匹配器:处理设备事件上报的触发器匹配逻辑 * * @author HUIHUI */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java index 0ee31a951e..fdaf68e3f1 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java @@ -9,9 +9,7 @@ import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatche import org.springframework.stereotype.Component; /** - * 设备属性上报触发器匹配器 - *

- * 处理设备属性数据上报的触发器匹配逻辑 + * 设备属性上报触发器匹配器:处理设备属性数据上报的触发器匹配逻辑 * * @author HUIHUI */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java index e0caba2d37..2c357fb1e2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java @@ -9,9 +9,7 @@ import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatche import org.springframework.stereotype.Component; /** - * 设备服务调用触发器匹配器 - *

- * 处理设备服务调用的触发器匹配逻辑 + * 设备服务调用触发器匹配器:处理设备服务调用的触发器匹配逻辑 * * @author HUIHUI */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java index f3a9f44cb0..b51716f4e3 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java @@ -9,9 +9,7 @@ import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatche import org.springframework.stereotype.Component; /** - * 设备状态更新触发器匹配器 - *

- * 处理设备上下线状态变更的触发器匹配逻辑 + * 设备状态更新触发器匹配器:处理设备上下线状态变更的触发器匹配逻辑 * * @author HUIHUI */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotSceneRuleTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotSceneRuleTriggerMatcher.java index 89de00a686..84ea57958f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotSceneRuleTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotSceneRuleTriggerMatcher.java @@ -6,12 +6,9 @@ import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcher; /** - * IoT 场景规则触发器匹配器接口 - *

- * 专门处理主触发条件的匹配逻辑,如设备消息类型、定时器等 - *

- * 触发器匹配器负责判断设备消息是否满足场景规则的主触发条件, - * 是场景规则执行的第一道门槛 + * IoT 场景规则触发器匹配器接口:专门处理主触发条件的匹配逻辑,如设备消息类型、定时器等 + * + * 触发器匹配器负责判断设备消息是否满足场景规则的主触发条件,是场景规则执行的第一道门槛 * * @author HUIHUI */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java index 794f8d6ae6..34cc59507d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java @@ -9,9 +9,8 @@ import org.quartz.CronExpression; import org.springframework.stereotype.Component; /** - * 定时触发器匹配器 - *

- * 处理定时触发的触发器匹配逻辑 + * 定时触发器匹配器:处理定时触发的触发器匹配逻辑 + * * 注意:定时触发器不依赖设备消息,主要用于定时任务场景 * * @author HUIHUI diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandler.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandler.java index ddb6367506..00a076494d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandler.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandler.java @@ -18,9 +18,7 @@ import java.util.List; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; /** - * IoT 场景规则定时触发器处理器 - *

- * 负责管理定时触发器的注册、更新、删除等操作 + * IoT 场景规则定时触发器处理器:负责管理定时触发器的注册、更新、删除等操作 * * @author HUIHUI */ @@ -37,19 +35,17 @@ public class IotSceneRuleTimerHandler { * @param sceneRule 场景规则 */ public void registerTimerTriggers(IotSceneRuleDO sceneRule) { + // 1. 过滤出定时触发器 if (sceneRule == null || CollUtil.isEmpty(sceneRule.getTriggers())) { return; } - - // 过滤出定时触发器 List timerTriggers = filterList(sceneRule.getTriggers(), trigger -> ObjUtil.equals(trigger.getType(), IotSceneRuleTriggerTypeEnum.TIMER.getType())); - if (CollUtil.isEmpty(timerTriggers)) { return; } - // 注册每个定时触发器 + // 2. 注册每个定时触发器 timerTriggers.forEach(trigger -> registerSingleTimerTrigger(sceneRule, trigger)); } @@ -63,23 +59,23 @@ public class IotSceneRuleTimerHandler { return; } - // 先删除旧的定时任务 + // 1. 先删除旧的定时任务 unregisterTimerTriggers(sceneRule.getId()); - // 如果场景规则已禁用,则不重新注册 + // 2.1 如果场景规则已禁用,则不重新注册 if (CommonStatusEnum.isDisable(sceneRule.getStatus())) { log.info("[updateTimerTriggers][场景规则({}) 已禁用,不注册定时触发器]", sceneRule.getId()); return; } - // 重新注册定时触发器 + // 2.2 重新注册定时触发器 registerTimerTriggers(sceneRule); } /** * 注销场景规则的定时触发器 * - * @param sceneRuleId 场景规则ID + * @param sceneRuleId 场景规则 ID */ public void unregisterTimerTriggers(Long sceneRuleId) { if (sceneRuleId == null) { @@ -98,7 +94,7 @@ public class IotSceneRuleTimerHandler { /** * 暂停场景规则的定时触发器 * - * @param sceneRuleId 场景规则ID + * @param sceneRuleId 场景规则 ID */ public void pauseTimerTriggers(Long sceneRuleId) { if (sceneRuleId == null) { @@ -114,25 +110,6 @@ public class IotSceneRuleTimerHandler { } } - /** - * 恢复场景规则的定时触发器 - * - * @param sceneRuleId 场景规则ID - */ - public void resumeTimerTriggers(Long sceneRuleId) { - if (sceneRuleId == null) { - return; - } - - String jobName = buildJobName(sceneRuleId); - try { - schedulerManager.resumeJob(jobName); - log.info("[resumeTimerTriggers][场景规则({}) 定时触发器恢复成功]", sceneRuleId); - } catch (SchedulerException e) { - log.error("[resumeTimerTriggers][场景规则({}) 定时触发器恢复失败]", sceneRuleId, e); - } - } - /** * 注册单个定时触发器 * @@ -146,18 +123,16 @@ public class IotSceneRuleTimerHandler { return; } - // 2. 构建任务名称和数据 - String jobName = buildJobName(sceneRule.getId()); - try { - // 3. 注册定时任务 + // 2.1 构建任务名称和数据 + String jobName = buildJobName(sceneRule.getId()); + // 2.2 注册定时任务 schedulerManager.addOrUpdateJob( IotSceneRuleJob.class, jobName, trigger.getCronExpression(), IotSceneRuleJob.buildJobDataMap(sceneRule.getId()) ); - log.info("[registerSingleTimerTrigger][场景规则({}) 定时触发器注册成功,CRON: {}]", sceneRule.getId(), trigger.getCronExpression()); } catch (SchedulerException e) { @@ -169,10 +144,11 @@ public class IotSceneRuleTimerHandler { /** * 构建任务名称 * - * @param sceneRuleId 场景规则ID + * @param sceneRuleId 场景规则 ID * @return 任务名称 */ private String buildJobName(Long sceneRuleId) { return "iot_scene_rule_timer_" + sceneRuleId; } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java index d73c926efa..b53e48da3a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java @@ -6,6 +6,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +// TODO @puhui999:建议改成 IotBaseConditionMatcherTest /** * Matcher 测试基类 * 提供通用的 Spring 测试配置 From 057f924bd165d3253e06bea213321b77e2b983c8 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 19 Sep 2025 10:59:32 +0800 Subject: [PATCH 06/11] =?UTF-8?q?refactor:=20=E3=80=90IoT=20=E7=89=A9?= =?UTF-8?q?=E8=81=94=E7=BD=91=E3=80=91=E7=BB=9F=E4=B8=80=E5=9C=BA=E6=99=AF?= =?UTF-8?q?=E8=81=94=E5=8A=A8=20=E2=80=98IoT=E2=80=99=20=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...va => IotCurrentTimeConditionMatcher.java} | 3 +- ...=> IotDevicePropertyConditionMatcher.java} | 2 +- ...va => IotDeviceStateConditionMatcher.java} | 2 +- ... => IotDeviceEventPostTriggerMatcher.java} | 2 +- ... IotDevicePropertyPostTriggerMatcher.java} | 2 +- ...IotDeviceServiceInvokeTriggerMatcher.java} | 2 +- ...> IotDeviceStateUpdateTriggerMatcher.java} | 2 +- ...tcher.java => IotTimerTriggerMatcher.java} | 2 +- ....java => IotBaseConditionMatcherTest.java} | 3 +- ...> IotCurrentTimeConditionMatcherTest.java} | 10 +- ...otDevicePropertyConditionMatcherTest.java} | 10 +- ...> IotDeviceStateConditionMatcherTest.java} | 10 +- ...IotDeviceEventPostTriggerMatcherTest.java} | 10 +- ...DevicePropertyPostTriggerMatcherTest.java} | 10 +- ...eviceServiceInvokeTriggerMatcherTest.java} | 10 +- ...tDeviceStateUpdateTriggerMatcherTest.java} | 22 +-- ...t.java => IotTimerTriggerMatcherTest.java} | 10 +- .../timer/IotSceneRuleTimerHandlerTest.java | 126 ++++++++++++++++++ 18 files changed, 175 insertions(+), 63 deletions(-) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/{CurrentTimeConditionMatcher.java => IotCurrentTimeConditionMatcher.java} (98%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/{DevicePropertyConditionMatcher.java => IotDevicePropertyConditionMatcher.java} (96%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/{DeviceStateConditionMatcher.java => IotDeviceStateConditionMatcher.java} (96%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/{DeviceEventPostTriggerMatcher.java => IotDeviceEventPostTriggerMatcher.java} (97%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/{DevicePropertyPostTriggerMatcher.java => IotDevicePropertyPostTriggerMatcher.java} (97%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/{DeviceServiceInvokeTriggerMatcher.java => IotDeviceServiceInvokeTriggerMatcher.java} (96%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/{DeviceStateUpdateTriggerMatcher.java => IotDeviceStateUpdateTriggerMatcher.java} (97%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/{TimerTriggerMatcher.java => IotTimerTriggerMatcher.java} (96%) rename yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/{BaseMatcherTest.java => IotBaseConditionMatcherTest.java} (88%) rename yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/{CurrentTimeConditionMatcherTest.java => IotCurrentTimeConditionMatcherTest.java} (96%) rename yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/{DevicePropertyConditionMatcherTest.java => IotDevicePropertyConditionMatcherTest.java} (97%) rename yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/{DeviceStateConditionMatcherTest.java => IotDeviceStateConditionMatcherTest.java} (97%) rename yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/{DeviceEventPostTriggerMatcherTest.java => IotDeviceEventPostTriggerMatcherTest.java} (97%) rename yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/{DevicePropertyPostTriggerMatcherTest.java => IotDevicePropertyPostTriggerMatcherTest.java} (96%) rename yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/{DeviceServiceInvokeTriggerMatcherTest.java => IotDeviceServiceInvokeTriggerMatcherTest.java} (97%) rename yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/{DeviceStateUpdateTriggerMatcherTest.java => IotDeviceStateUpdateTriggerMatcherTest.java} (92%) rename yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/{TimerTriggerMatcherTest.java => IotTimerTriggerMatcherTest.java} (96%) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandlerTest.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcher.java similarity index 98% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcher.java index cb85f36203..2083bebac9 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcher.java @@ -16,7 +16,6 @@ import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.List; -// TODO @puhui999:是不是 IoT 的前缀,都加下哈; /** * 当前时间条件匹配器:处理时间相关的子条件匹配逻辑 * @@ -24,7 +23,7 @@ import java.util.List; */ @Component @Slf4j -public class CurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher { +public class IotCurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher { /** * 时间格式化器 - HH:mm:ss diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcher.java similarity index 96% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcher.java index f4316c0b5d..c130c55438 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcher.java @@ -15,7 +15,7 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatcher { +public class IotDevicePropertyConditionMatcher implements IotSceneRuleConditionMatcher { @Override public IotSceneRuleConditionTypeEnum getSupportedConditionType() { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcher.java similarity index 96% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcher.java index ceb1ee8c8b..232812270f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcher.java @@ -13,7 +13,7 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher { +public class IotDeviceStateConditionMatcher implements IotSceneRuleConditionMatcher { @Override public IotSceneRuleConditionTypeEnum getSupportedConditionType() { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcher.java similarity index 97% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcher.java index 2a843d1c2c..825b5eae1d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcher.java @@ -15,7 +15,7 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher { +public class IotDeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher { @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcher.java similarity index 97% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcher.java index fdaf68e3f1..27cb02a1a5 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcher.java @@ -14,7 +14,7 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatcher { +public class IotDevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatcher { @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java similarity index 96% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java index 2c357fb1e2..d3a73f806b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java @@ -14,7 +14,7 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher { +public class IotDeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher { @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcher.java similarity index 97% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcher.java index b51716f4e3..6b8c73a501 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcher.java @@ -14,7 +14,7 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher { +public class IotDeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher { @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcher.java similarity index 96% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcher.java index 34cc59507d..f980c2471b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcher.java @@ -16,7 +16,7 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class TimerTriggerMatcher implements IotSceneRuleTriggerMatcher { +public class IotTimerTriggerMatcher implements IotSceneRuleTriggerMatcher { @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotBaseConditionMatcherTest.java similarity index 88% rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotBaseConditionMatcherTest.java index b53e48da3a..5be63b57d2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotBaseConditionMatcherTest.java @@ -6,7 +6,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -// TODO @puhui999:建议改成 IotBaseConditionMatcherTest /** * Matcher 测试基类 * 提供通用的 Spring 测试配置 @@ -14,7 +13,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; * @author HUIHUI */ @SpringJUnitConfig -public abstract class BaseMatcherTest { +public abstract class IotBaseConditionMatcherTest { /** * 注入一下 SpringUtil,解析 EL 表达式时需要 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcherTest.java similarity index 96% rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcherTest.java index f14da99a56..586d948cd0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotCurrentTimeConditionMatcherTest.java @@ -4,7 +4,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -16,17 +16,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString import static org.junit.jupiter.api.Assertions.*; /** - * {@link CurrentTimeConditionMatcher} 的单元测试 + * {@link IotCurrentTimeConditionMatcher} 的单元测试 * * @author HUIHUI */ -public class CurrentTimeConditionMatcherTest extends BaseMatcherTest { +public class IotCurrentTimeConditionMatcherTest extends IotBaseConditionMatcherTest { - private CurrentTimeConditionMatcher matcher; + private IotCurrentTimeConditionMatcher matcher; @BeforeEach public void setUp() { - matcher = new CurrentTimeConditionMatcher(); + matcher = new IotCurrentTimeConditionMatcher(); } @Test diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcherTest.java similarity index 97% rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcherTest.java index ddfebc66be..5a40995567 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDevicePropertyConditionMatcherTest.java @@ -4,7 +4,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -15,17 +15,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId import static org.junit.jupiter.api.Assertions.*; /** - * {@link DevicePropertyConditionMatcher} 的单元测试 + * {@link IotDevicePropertyConditionMatcher} 的单元测试 * * @author HUIHUI */ -public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { +public class IotDevicePropertyConditionMatcherTest extends IotBaseConditionMatcherTest { - private DevicePropertyConditionMatcher matcher; + private IotDevicePropertyConditionMatcher matcher; @BeforeEach public void setUp() { - matcher = new DevicePropertyConditionMatcher(); + matcher = new IotDevicePropertyConditionMatcher(); } @Test diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java similarity index 97% rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java index d54575ba41..da59077a6e 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -14,17 +14,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString import static org.junit.jupiter.api.Assertions.*; /** - * {@link DeviceStateConditionMatcher} 的单元测试 + * {@link IotDeviceStateConditionMatcher} 的单元测试 * * @author HUIHUI */ -public class DeviceStateConditionMatcherTest extends BaseMatcherTest { +public class IotDeviceStateConditionMatcherTest extends IotBaseConditionMatcherTest { - private DeviceStateConditionMatcher matcher; + private IotDeviceStateConditionMatcher matcher; @BeforeEach public void setUp() { - matcher = new DeviceStateConditionMatcher(); + matcher = new IotDeviceStateConditionMatcher(); } @Test diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcherTest.java similarity index 97% rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcherTest.java index 4569d439cc..9cf51421fe 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceEventPostTriggerMatcherTest.java @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -18,17 +18,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString import static org.junit.jupiter.api.Assertions.*; /** - * {@link DeviceEventPostTriggerMatcher} 的单元测试 + * {@link IotDeviceEventPostTriggerMatcher} 的单元测试 * * @author HUIHUI */ -public class DeviceEventPostTriggerMatcherTest extends BaseMatcherTest { +public class IotDeviceEventPostTriggerMatcherTest extends IotBaseConditionMatcherTest { - private DeviceEventPostTriggerMatcher matcher; + private IotDeviceEventPostTriggerMatcher matcher; @BeforeEach public void setUp() { - matcher = new DeviceEventPostTriggerMatcher(); + matcher = new IotDeviceEventPostTriggerMatcher(); } @Test diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcherTest.java similarity index 96% rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcherTest.java index eb191eb927..fb155763aa 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDevicePropertyPostTriggerMatcherTest.java @@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -20,17 +20,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString import static org.junit.jupiter.api.Assertions.*; /** - * {@link DevicePropertyPostTriggerMatcher} 的单元测试 + * {@link IotDevicePropertyPostTriggerMatcher} 的单元测试 * * @author HUIHUI */ -public class DevicePropertyPostTriggerMatcherTest extends BaseMatcherTest { +public class IotDevicePropertyPostTriggerMatcherTest extends IotBaseConditionMatcherTest { - private DevicePropertyPostTriggerMatcher matcher; + private IotDevicePropertyPostTriggerMatcher matcher; @BeforeEach public void setUp() { - matcher = new DevicePropertyPostTriggerMatcher(); + matcher = new IotDevicePropertyPostTriggerMatcher(); } @Test diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcherTest.java similarity index 97% rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcherTest.java index cfa36605f8..a515f1268e 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcherTest.java @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -18,17 +18,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString import static org.junit.jupiter.api.Assertions.*; /** - * {@link DeviceServiceInvokeTriggerMatcher} 的单元测试 + * {@link IotDeviceServiceInvokeTriggerMatcher} 的单元测试 * * @author HUIHUI */ -public class DeviceServiceInvokeTriggerMatcherTest extends BaseMatcherTest { +public class IotDeviceServiceInvokeTriggerMatcherTest extends IotBaseConditionMatcherTest { - private DeviceServiceInvokeTriggerMatcher matcher; + private IotDeviceServiceInvokeTriggerMatcher matcher; @BeforeEach public void setUp() { - matcher = new DeviceServiceInvokeTriggerMatcher(); + matcher = new IotDeviceServiceInvokeTriggerMatcher(); } @Test diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java similarity index 92% rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java index bee6e072e4..2e8b17a353 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java @@ -1,42 +1,30 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; -import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static org.junit.jupiter.api.Assertions.*; /** - * {@link DeviceStateUpdateTriggerMatcher} 的单元测试 + * {@link IotDeviceStateUpdateTriggerMatcher} 的单元测试 * * @author HUIHUI */ -@SpringJUnitConfig(DeviceStateUpdateTriggerMatcherTest.TestConfig.class) -public class DeviceStateUpdateTriggerMatcherTest { +public class IotDeviceStateUpdateTriggerMatcherTest extends IotBaseConditionMatcherTest { - @Configuration - static class TestConfig { - @Bean - public SpringUtil springUtil() { - return new SpringUtil(); - } - } - - private DeviceStateUpdateTriggerMatcher matcher; + private IotDeviceStateUpdateTriggerMatcher matcher; @BeforeEach public void setUp() { - matcher = new DeviceStateUpdateTriggerMatcher(); + matcher = new IotDeviceStateUpdateTriggerMatcher(); } @Test diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcherTest.java similarity index 96% rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcherTest.java index f332f3097b..df47e6c28f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotTimerTriggerMatcherTest.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotBaseConditionMatcherTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -12,17 +12,17 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString import static org.junit.jupiter.api.Assertions.*; /** - * {@link TimerTriggerMatcher} 的单元测试 + * {@link IotTimerTriggerMatcher} 的单元测试 * * @author HUIHUI */ -public class TimerTriggerMatcherTest extends BaseMatcherTest { +public class IotTimerTriggerMatcherTest extends IotBaseConditionMatcherTest { - private TimerTriggerMatcher matcher; + private IotTimerTriggerMatcher matcher; @BeforeEach public void setUp() { - matcher = new TimerTriggerMatcher(); + matcher = new IotTimerTriggerMatcher(); } @Test diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandlerTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandlerTest.java new file mode 100644 index 0000000000..3e38e93616 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandlerTest.java @@ -0,0 +1,126 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.timer; + +import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; +import cn.iocoder.yudao.module.iot.job.rule.IotSceneRuleJob; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.quartz.SchedulerException; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +/** + * {@link IotSceneRuleTimerHandler} 的单元测试类 + * + * @author HUIHUI + */ +@ExtendWith(MockitoExtension.class) +public class IotSceneRuleTimerHandlerTest { + + @Mock + private IotSchedulerManager schedulerManager; + + @InjectMocks + private IotSceneRuleTimerHandler timerHandler; + + @BeforeEach + void setUp() { + // 重置 Mock 对象 + reset(schedulerManager); + } + + @Test + public void testRegisterTimerTriggers_success() throws SchedulerException { + // 准备参数 + Long sceneRuleId = 1L; + IotSceneRuleDO sceneRule = new IotSceneRuleDO(); + sceneRule.setId(sceneRuleId); + sceneRule.setStatus(0); // 0 表示启用 + // 创建定时触发器 + IotSceneRuleDO.Trigger timerTrigger = new IotSceneRuleDO.Trigger(); + timerTrigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); + timerTrigger.setCronExpression("0 0 12 * * ?"); // 每天中午12点 + sceneRule.setTriggers(ListUtil.toList(timerTrigger)); + + // 调用 + timerHandler.registerTimerTriggers(sceneRule); + + // 验证 + verify(schedulerManager, times(1)).addOrUpdateJob( + eq(IotSceneRuleJob.class), + eq("iot_scene_rule_timer_" + sceneRuleId), + eq("0 0 12 * * ?"), + eq(IotSceneRuleJob.buildJobDataMap(sceneRuleId)) + ); + } + + @Test + public void testRegisterTimerTriggers_noTimerTrigger() throws SchedulerException { + // 准备参数 - 没有定时触发器 + IotSceneRuleDO sceneRule = new IotSceneRuleDO(); + sceneRule.setStatus(0); // 0 表示启用 + // 创建非定时触发器 + IotSceneRuleDO.Trigger deviceTrigger = new IotSceneRuleDO.Trigger(); + deviceTrigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); + sceneRule.setTriggers(ListUtil.toList(deviceTrigger)); + + // 调用 + timerHandler.registerTimerTriggers(sceneRule); + + // 验证 - 不应该调用调度器 + verify(schedulerManager, never()).addOrUpdateJob(any(), any(), any(), any()); + } + + @Test + public void testRegisterTimerTriggers_emptyCronExpression() throws SchedulerException { + // 准备参数 - CRON 表达式为空 + Long sceneRuleId = 2L; + IotSceneRuleDO sceneRule = new IotSceneRuleDO(); + sceneRule.setId(sceneRuleId); + sceneRule.setStatus(0); // 0 表示启用 + // 创建定时触发器但没有 CRON 表达式 + IotSceneRuleDO.Trigger timerTrigger = new IotSceneRuleDO.Trigger(); + timerTrigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); + timerTrigger.setCronExpression(""); // 空的 CRON 表达式 + sceneRule.setTriggers(ListUtil.toList(timerTrigger)); + + // 调用 + timerHandler.registerTimerTriggers(sceneRule); + + // 验证 - 不应该调用调度器 + verify(schedulerManager, never()).addOrUpdateJob(any(), any(), any(), any()); + } + + @Test + public void testUnregisterTimerTriggers_success() throws SchedulerException { + // 准备参数 + Long sceneRuleId = 3L; + + // 调用 + timerHandler.unregisterTimerTriggers(sceneRuleId); + + // 验证 + verify(schedulerManager, times(1)).deleteJob("iot_scene_rule_timer_" + sceneRuleId); + } + + @Test + public void testPauseTimerTriggers_success() throws SchedulerException { + // 准备参数 + Long sceneRuleId = 4L; + + // 调用 + timerHandler.pauseTimerTriggers(sceneRuleId); + + // 验证 + verify(schedulerManager, times(1)).pauseJob("iot_scene_rule_timer_" + sceneRuleId); + } + +} From 997bfe8fa116c0f2bb0c4149d08d95bd37e52ab6 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 19 Sep 2025 14:37:25 +0800 Subject: [PATCH 07/11] =?UTF-8?q?feat:=20=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E5=9C=BA=E6=99=AF=E8=81=94=E5=8A=A8=E8=A7=84?= =?UTF-8?q?=E5=88=99=E7=9A=84=E6=95=B0=E6=8D=AE=E7=BC=93=E5=AD=98=EF=BC=8C?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20Spring=20Cache=20=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iot/dal/redis/RedisKeyConstants.java | 8 ++++++ .../rule/scene/IotSceneRuleServiceImpl.java | 25 +++++++++++-------- .../IotDeviceServiceInvokeTriggerMatcher.java | 2 +- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java index 1187677e54..c8041a673c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java @@ -76,4 +76,12 @@ public interface RedisKeyConstants { */ String DATA_SINK = "iot:data_sink"; + /** + * 场景联动规则的数据缓存,使用 Spring Cache 操作 + *

+ * KEY 格式:scene_rule_list_${productId}_${deviceId} + * VALUE 数据类型:String 数组(JSON),即 {@link cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO} 列表 + */ + String SCENE_RULE_LIST = "iot:scene_rule_list"; + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java index c631e34586..a29ff98616 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java @@ -16,6 +16,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; +import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; import cn.iocoder.yudao.module.iot.service.product.IotProductService; @@ -24,6 +25,8 @@ import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatche import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotSceneRuleTimerHandler; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -60,6 +63,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { private IotSceneRuleTimerHandler timerHandler; @Override + @CacheEvict(value = RedisKeyConstants.SCENE_RULE_LIST, allEntries = true) public Long createSceneRule(IotSceneRuleSaveReqVO createReqVO) { IotSceneRuleDO sceneRule = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class); sceneRuleMapper.insert(sceneRule); @@ -71,6 +75,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { } @Override + @CacheEvict(value = RedisKeyConstants.SCENE_RULE_LIST, allEntries = true) public void updateSceneRule(IotSceneRuleSaveReqVO updateReqVO) { // 校验存在 validateSceneRuleExists(updateReqVO.getId()); @@ -83,6 +88,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { } @Override + @CacheEvict(value = RedisKeyConstants.SCENE_RULE_LIST, allEntries = true) public void updateSceneRuleStatus(Long id, Integer status) { // 1. 校验存在 validateSceneRuleExists(id); @@ -105,6 +111,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { } @Override + @CacheEvict(value = RedisKeyConstants.SCENE_RULE_LIST, allEntries = true) public void deleteSceneRule(Long id) { // 1. 校验存在 validateSceneRuleExists(id); @@ -149,15 +156,12 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { return sceneRuleMapper.selectListByStatus(status); } - // TODO @puhui999:缓存待实现 @Override + @Cacheable(value = RedisKeyConstants.SCENE_RULE_LIST, key = "#productId + '_' + #deviceId ") @TenantIgnore // 忽略租户隔离:因为 IotSceneRuleMessageHandler 调用时,一般未传递租户,所以需要忽略 public List getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId) { // 1. 查询启用状态的规则场景 - // TODO @puhui999:这里查询 enable 的; - List list = sceneRuleMapper.selectList(); - List enabledList = filterList(list, - sceneRule -> CommonStatusEnum.isEnable(sceneRule.getStatus())); + List enabledList = sceneRuleMapper.selectList(IotSceneRuleDO::getStatus, CommonStatusEnum.ENABLE.getStatus()); // 2. 根据 productKey 和 deviceName 进行匹配 return filterList(enabledList, sceneRule -> { @@ -190,9 +194,10 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @Override public void executeSceneRuleByDevice(IotDeviceMessage message) { - // TODO @puhui999:这里的 tenantId,通过设备获取; - TenantUtils.execute(message.getTenantId(), () -> { - // 1. 获得设备匹配的规则场景 + // 1.1 这里的 tenantId,通过设备获取; + IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId()); + TenantUtils.execute(device.getTenantId(), () -> { + // 1.2 获得设备匹配的规则场景 List sceneRules = getMatchedSceneRuleListByMessage(message); if (CollUtil.isEmpty(sceneRules)) { return; @@ -238,14 +243,14 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { // 1. 匹配设备 // TODO 缓存 @puhui999:可能需要 getSelf() // 1.1 通过 deviceId 获取设备信息 - IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId()); + IotDeviceDO device = getSelf().deviceService.getDeviceFromCache(message.getDeviceId()); if (device == null) { log.warn("[getMatchedSceneRuleListByMessage][设备({}) 不存在]", message.getDeviceId()); return List.of(); } // 1.2 通过 productId 获取产品信息 - IotProductDO product = productService.getProductFromCache(device.getProductId()); + IotProductDO product = getSelf().productService.getProductFromCache(device.getProductId()); if (product == null) { log.warn("[getMatchedSceneRuleListByMessage][产品({}) 不存在]", device.getProductId()); return List.of(); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java index d3a73f806b..b5fa0330dc 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java @@ -44,7 +44,7 @@ public class IotDeviceServiceInvokeTriggerMatcher implements IotSceneRuleTrigger // 2. 对于服务调用触发器,通常只需要匹配服务标识符即可 // 不需要检查操作符和值,因为服务调用本身就是触发条件 - // TODO @puhui999: 服务调用时校验输入参数是否匹配条件 + // TODO @puhui999: 服务调用时校验输入参数是否匹配条件? IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); return true; } From c5b4968e5502325ea6e8627f59b8c17a8ccaeb44 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 19 Sep 2025 18:05:47 +0800 Subject: [PATCH 08/11] =?UTF-8?q?feat:=20=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E6=95=B0=E6=8D=AE=E6=B5=81=E8=BD=AC=E7=9B=AE?= =?UTF-8?q?=E7=9A=84=20TCP=20=E6=89=A7=E8=A1=8C=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/IotAbstractDataSinkConfig.java | 2 + .../rule/config/IotDataSinkTcpConfig.java | 63 ++++++ .../config/IotDataSinkWebSocketConfig.java | 87 +++++++++ .../data/action/IotTcpDataRuleAction.java | 97 +++++++++ .../rule/data/action/tcp/IotTcpClient.java | 184 ++++++++++++++++++ .../data/action/IotTcpDataRuleActionTest.java | 161 +++++++++++++++ 6 files changed, 594 insertions(+) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkTcpConfig.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkWebSocketConfig.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleAction.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/tcp/IotTcpClient.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleActionTest.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotAbstractDataSinkConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotAbstractDataSinkConfig.java index 68a8fd699b..b42e1c0a42 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotAbstractDataSinkConfig.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotAbstractDataSinkConfig.java @@ -17,6 +17,8 @@ import lombok.Data; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true) @JsonSubTypes({ @JsonSubTypes.Type(value = IotDataSinkHttpConfig.class, name = "1"), + @JsonSubTypes.Type(value = IotDataSinkTcpConfig.class, name = "2"), + @JsonSubTypes.Type(value = IotDataSinkWebSocketConfig.class, name = "3"), @JsonSubTypes.Type(value = IotDataSinkMqttConfig.class, name = "10"), @JsonSubTypes.Type(value = IotDataSinkRedisConfig.class, name = "21"), @JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"), diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkTcpConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkTcpConfig.java new file mode 100644 index 0000000000..3d96f11ceb --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkTcpConfig.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config; + +import lombok.Data; + +/** + * IoT TCP 配置 {@link IotAbstractDataSinkConfig} 实现类 + * + * @author HUIHUI + */ +@Data +public class IotDataSinkTcpConfig extends IotAbstractDataSinkConfig { + + /** + * TCP 服务器地址 + */ + private String host; + + /** + * TCP 服务器端口 + */ + private Integer port; + + /** + * 连接超时时间(毫秒) + */ + private Integer connectTimeoutMs = 5000; + + /** + * 读取超时时间(毫秒) + */ + private Integer readTimeoutMs = 10000; + + /** + * 是否启用 SSL + */ + private Boolean ssl = false; + + /** + * SSL 证书路径(当 ssl=true 时需要) + */ + private String sslCertPath; + + /** + * 数据格式:JSON 或 BINARY + */ + private String dataFormat = "JSON"; + + /** + * 心跳间隔时间(毫秒),0 表示不启用心跳 + */ + private Long heartbeatIntervalMs = 30000L; + + /** + * 重连间隔时间(毫秒) + */ + private Long reconnectIntervalMs = 5000L; + + /** + * 最大重连次数 + */ + private Integer maxReconnectAttempts = 3; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkWebSocketConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkWebSocketConfig.java new file mode 100644 index 0000000000..f1b7e86d86 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkWebSocketConfig.java @@ -0,0 +1,87 @@ +package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config; + +import lombok.Data; + +/** + * IoT WebSocket 配置 {@link IotAbstractDataSinkConfig} 实现类 + *

+ * 配置设备消息通过 WebSocket 协议发送到外部 WebSocket 服务器 + * 支持 WebSocket (ws://) 和 WebSocket Secure (wss://) 连接 + * + * @author HUIHUI + */ +@Data +public class IotDataSinkWebSocketConfig extends IotAbstractDataSinkConfig { + + /** + * WebSocket 服务器地址 + * 例如:ws://localhost:8080/ws 或 wss://example.com/ws + */ + private String serverUrl; + + /** + * 连接超时时间(毫秒) + */ + private Integer connectTimeoutMs = 5000; + + /** + * 发送超时时间(毫秒) + */ + private Integer sendTimeoutMs = 10000; + + /** + * 心跳间隔时间(毫秒),0 表示不启用心跳 + */ + private Long heartbeatIntervalMs = 30000L; + + /** + * 心跳消息内容(JSON 格式) + */ + private String heartbeatMessage = "{\"type\":\"heartbeat\"}"; + + /** + * 子协议列表(逗号分隔) + */ + private String subprotocols; + + /** + * 自定义请求头(JSON 格式) + */ + private String customHeaders; + + /** + * 是否启用 SSL 证书验证(仅对 wss:// 生效) + */ + private Boolean verifySslCert = true; + + /** + * 数据格式:JSON 或 TEXT + */ + private String dataFormat = "JSON"; + + /** + * 重连间隔时间(毫秒) + */ + private Long reconnectIntervalMs = 5000L; + + /** + * 最大重连次数 + */ + private Integer maxReconnectAttempts = 3; + + /** + * 是否启用压缩 + */ + private Boolean enableCompression = false; + + /** + * 消息发送重试次数 + */ + private Integer sendRetryCount = 1; + + /** + * 消息发送重试间隔(毫秒) + */ + private Long sendRetryIntervalMs = 1000L; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleAction.java new file mode 100644 index 0000000000..e9810eb08d --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleAction.java @@ -0,0 +1,97 @@ +package cn.iocoder.yudao.module.iot.service.rule.data.action; + +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkTcpConfig; +import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.data.action.tcp.IotTcpClient; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +/** + * TCP 的 {@link IotDataRuleAction} 实现类 + *

+ * 负责将设备消息发送到外部 TCP 服务器 + * 支持普通 TCP 和 SSL TCP 连接,支持 JSON 和 BINARY 数据格式 + * 使用连接池管理 TCP 连接,提高性能和资源利用率 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class IotTcpDataRuleAction extends + IotDataRuleCacheableAction { + + private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(5); + private static final Duration SEND_TIMEOUT = Duration.ofSeconds(10); + + @Override + public Integer getType() { + return IotDataSinkTypeEnum.TCP.getType(); + } + + @Override + protected IotTcpClient initProducer(IotDataSinkTcpConfig config) throws Exception { + // 1. 参数校验 + if (config.getHost() == null || config.getHost().trim().isEmpty()) { + throw new IllegalArgumentException("TCP 服务器地址不能为空"); + } + if (config.getPort() == null || config.getPort() <= 0 || config.getPort() > 65535) { + throw new IllegalArgumentException("TCP 服务器端口无效"); + } + + // 2. 创建 TCP 客户端 + IotTcpClient tcpClient = new IotTcpClient( + config.getHost(), + config.getPort(), + config.getConnectTimeoutMs(), + config.getReadTimeoutMs(), + config.getSsl(), + config.getSslCertPath(), + config.getDataFormat() + ); + + // 3. 连接服务器 + tcpClient.connect(); + + log.info("[initProducer][TCP 客户端创建并连接成功,服务器: {}:{},SSL: {},数据格式: {}]", + config.getHost(), config.getPort(), config.getSsl(), config.getDataFormat()); + + return tcpClient; + } + + @Override + protected void closeProducer(IotTcpClient producer) throws Exception { + if (producer != null) { + producer.close(); + } + } + + @Override + protected void execute(IotDeviceMessage message, IotDataSinkTcpConfig config) throws Exception { + try { + // 1. 获取或创建 TCP 客户端 + IotTcpClient tcpClient = getProducer(config); + + // 2. 检查连接状态,如果断开则重新连接 + if (!tcpClient.isConnected()) { + log.warn("[execute][TCP 连接已断开,尝试重新连接,服务器: {}:{}]", config.getHost(), config.getPort()); + tcpClient.connect(); + } + + // 3. 发送消息并等待结果 + tcpClient.sendMessage(message); + + // 4. 记录发送成功日志 + log.info("[execute][message({}) config({}) 发送成功,TCP 服务器: {}:{}]", + message, config, config.getHost(), config.getPort()); + + } catch (Exception e) { + log.error("[execute][message({}) config({}) 发送失败,TCP 服务器: {}:{}]", + message, config, config.getHost(), config.getPort(), e); + throw e; + } + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/tcp/IotTcpClient.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/tcp/IotTcpClient.java new file mode 100644 index 0000000000..052c43ed4c --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/tcp/IotTcpClient.java @@ -0,0 +1,184 @@ +package cn.iocoder.yudao.module.iot.service.rule.data.action.tcp; + +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import lombok.extern.slf4j.Slf4j; + +import javax.net.ssl.SSLSocketFactory; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * IoT TCP 客户端 + *

+ * 负责与外部 TCP 服务器建立连接并发送设备消息 + * 支持 JSON 和 BINARY 两种数据格式,支持 SSL 加密连接 + * + * @author HUIHUI + */ +@Slf4j +public class IotTcpClient { + + private final String host; + private final Integer port; + private final Integer connectTimeoutMs; + private final Integer readTimeoutMs; + private final Boolean ssl; + private final String sslCertPath; + private final String dataFormat; + + private Socket socket; + private OutputStream outputStream; + private BufferedReader reader; + private final AtomicBoolean connected = new AtomicBoolean(false); + + public IotTcpClient(String host, Integer port, Integer connectTimeoutMs, Integer readTimeoutMs, + Boolean ssl, String sslCertPath, String dataFormat) { + this.host = host; + this.port = port; + this.connectTimeoutMs = connectTimeoutMs != null ? connectTimeoutMs : 5000; + this.readTimeoutMs = readTimeoutMs != null ? readTimeoutMs : 10000; + this.ssl = ssl != null ? ssl : false; + this.sslCertPath = sslCertPath; + this.dataFormat = dataFormat != null ? dataFormat : "JSON"; + } + + /** + * 连接到 TCP 服务器 + */ + public void connect() throws Exception { + if (connected.get()) { + log.warn("[connect][TCP 客户端已经连接,无需重复连接]"); + return; + } + + try { + if (ssl) { + // SSL 连接 + SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); + socket = sslSocketFactory.createSocket(); + } else { + // 普通连接 + socket = new Socket(); + } + + // 连接服务器 + socket.connect(new InetSocketAddress(host, port), connectTimeoutMs); + socket.setSoTimeout(readTimeoutMs); + + // 获取输入输出流 + outputStream = socket.getOutputStream(); + reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)); + + connected.set(true); + log.info("[connect][TCP 客户端连接成功,服务器地址: {}:{}]", host, port); + + } catch (Exception e) { + close(); + log.error("[connect][TCP 客户端连接失败,服务器地址: {}:{}]", host, port, e); + throw e; + } + } + + /** + * 发送设备消息 + * + * @param message 设备消息 + * @throws Exception 发送异常 + */ + public void sendMessage(IotDeviceMessage message) throws Exception { + if (!connected.get()) { + throw new IllegalStateException("TCP 客户端未连接"); + } + + try { + String messageData; + if ("JSON".equalsIgnoreCase(dataFormat)) { + // JSON 格式 + messageData = JsonUtils.toJsonString(message); + } else { + // BINARY 格式(这里简化为字符串,实际可能需要自定义二进制协议) + messageData = message.toString(); + } + + // 发送消息 + outputStream.write(messageData.getBytes(StandardCharsets.UTF_8)); + outputStream.write('\n'); // 添加换行符作为消息分隔符 + outputStream.flush(); + + log.debug("[sendMessage][发送消息成功,设备 ID: {},消息长度: {}]", + message.getDeviceId(), messageData.length()); + + } catch (Exception e) { + log.error("[sendMessage][发送消息失败,设备 ID: {}]", message.getDeviceId(), e); + throw e; + } + } + + /** + * 关闭连接 + */ + public void close() { + if (!connected.get()) { + return; + } + + try { + // 关闭资源 + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + log.warn("[close][关闭输入流失败]", e); + } + } + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + log.warn("[close][关闭输出流失败]", e); + } + } + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + log.warn("[close][关闭 Socket 失败]", e); + } + } + + connected.set(false); + log.info("[close][TCP 客户端连接已关闭,服务器地址: {}:{}]", host, port); + + } catch (Exception e) { + log.error("[close][关闭 TCP 客户端连接异常]", e); + } + } + + /** + * 检查连接状态 + * + * @return 是否已连接 + */ + public boolean isConnected() { + return connected.get() && socket != null && !socket.isClosed(); + } + + @Override + public String toString() { + return "IotTcpClient{" + + "host='" + host + '\'' + + ", port=" + port + + ", ssl=" + ssl + + ", dataFormat='" + dataFormat + '\'' + + ", connected=" + connected.get() + + '}'; + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleActionTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleActionTest.java new file mode 100644 index 0000000000..3a7ee1cf73 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleActionTest.java @@ -0,0 +1,161 @@ +package cn.iocoder.yudao.module.iot.service.rule.data.action; + +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkTcpConfig; +import cn.iocoder.yudao.module.iot.service.rule.data.action.tcp.IotTcpClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * {@link IotTcpDataRuleAction} 的单元测试 + * + * @author HUIHUI + */ +class IotTcpDataRuleActionTest { + + private IotTcpDataRuleAction tcpDataRuleAction; + + @Mock + private IotTcpClient mockTcpClient; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + tcpDataRuleAction = new IotTcpDataRuleAction(); + } + + @Test + void testGetType() { + // 准备参数 + Integer expectedType = 2; // 数据接收类型枚举中 TCP 类型的值 + + // 调用方法 + Integer actualType = tcpDataRuleAction.getType(); + + // 断言结果 + assertEquals(expectedType, actualType); + } + + @Test + void testInitProducer_Success() throws Exception { + // 准备参数 + IotDataSinkTcpConfig config = new IotDataSinkTcpConfig(); + config.setHost("localhost"); + config.setPort(8080); + config.setDataFormat("JSON"); + config.setSsl(false); + + // 调用方法 & 断言结果 + // 此测试需要实际的 TCP 连接,在单元测试中可能不可用 + // 目前我们只验证配置是否有效 + assertNotNull(config.getHost()); + assertTrue(config.getPort() > 0 && config.getPort() <= 65535); + } + + @Test + void testInitProducer_InvalidHost() { + // 准备参数 + IotDataSinkTcpConfig config = new IotDataSinkTcpConfig(); + config.setHost(""); + config.setPort(8080); + + // 调用方法 & 断言结果 + IotTcpDataRuleAction action = new IotTcpDataRuleAction(); + + // 测试验证逻辑(通常在 initProducer 方法中) + assertThrows(IllegalArgumentException.class, () -> { + if (config.getHost() == null || config.getHost().trim().isEmpty()) { + throw new IllegalArgumentException("TCP 服务器地址不能为空"); + } + }); + } + + @Test + void testInitProducer_InvalidPort() { + // 准备参数 + IotDataSinkTcpConfig config = new IotDataSinkTcpConfig(); + config.setHost("localhost"); + config.setPort(-1); + + // 调用方法 & 断言结果 + assertThrows(IllegalArgumentException.class, () -> { + if (config.getPort() == null || config.getPort() <= 0 || config.getPort() > 65535) { + throw new IllegalArgumentException("TCP 服务器端口无效"); + } + }); + } + + @Test + void testCloseProducer() throws Exception { + // 准备参数 + IotTcpClient client = mock(IotTcpClient.class); + + // 调用方法 + tcpDataRuleAction.closeProducer(client); + + // 断言结果 + verify(client, times(1)).close(); + } + + @Test + void testExecute_WithValidConfig() { + // 准备参数 + IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.report", + "{\"temperature\": 25.5, \"humidity\": 60}"); + + IotDataSinkTcpConfig config = new IotDataSinkTcpConfig(); + config.setHost("localhost"); + config.setPort(8080); + config.setDataFormat("JSON"); + + // 调用方法 & 断言结果 + // 通常这需要实际的 TCP 连接 + // 在单元测试中,我们只验证输入参数 + assertNotNull(message); + assertNotNull(config); + assertNotNull(config.getHost()); + assertTrue(config.getPort() > 0); + } + + @Test + void testConfig_DefaultValues() { + // 准备参数 + IotDataSinkTcpConfig config = new IotDataSinkTcpConfig(); + + // 调用方法 & 断言结果 + // 验证默认值 + assertEquals("JSON", config.getDataFormat()); + assertEquals(5000, config.getConnectTimeoutMs()); + assertEquals(10000, config.getReadTimeoutMs()); + assertEquals(false, config.getSsl()); + assertEquals(30000L, config.getHeartbeatIntervalMs()); + assertEquals(5000L, config.getReconnectIntervalMs()); + assertEquals(3, config.getMaxReconnectAttempts()); + } + + @Test + void testMessageSerialization() { + // 准备参数 + IotDeviceMessage message = IotDeviceMessage.builder() + .deviceId(123L) + .method("thing.property.report") + .params("{\"temperature\": 25.5}") + .build(); + + // 调用方法 + String json = JsonUtils.toJsonString(message); + + // 断言结果 + assertNotNull(json); + assertTrue(json.contains("\"deviceId\":123")); + assertTrue(json.contains("\"method\":\"thing.property.report\"")); + assertTrue(json.contains("\"temperature\":25.5")); + } + +} \ No newline at end of file From 5ae0224b553c30f05f3658d3ebe8bec0cfab56ee Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 20 Sep 2025 16:15:33 +0800 Subject: [PATCH 09/11] =?UTF-8?q?review=EF=BC=9A=E3=80=90iot=20=E7=89=A9?= =?UTF-8?q?=E8=81=94=E7=BD=91=E3=80=91=E5=9C=BA=E6=99=AF=E8=81=94=E5=8A=A8?= =?UTF-8?q?=E7=9A=84=E7=BC=93=E5=AD=98=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/action/IotTcpDataRuleAction.java | 20 +++++++------------ .../rule/data/action/tcp/IotTcpClient.java | 8 ++++---- .../data/action/IotTcpDataRuleActionTest.java | 19 +++++++++--------- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleAction.java index e9810eb08d..4db6dc205a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleAction.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleAction.java @@ -33,7 +33,7 @@ public class IotTcpDataRuleAction extends @Override protected IotTcpClient initProducer(IotDataSinkTcpConfig config) throws Exception { - // 1. 参数校验 + // 1.1 参数校验 if (config.getHost() == null || config.getHost().trim().isEmpty()) { throw new IllegalArgumentException("TCP 服务器地址不能为空"); } @@ -41,7 +41,7 @@ public class IotTcpDataRuleAction extends throw new IllegalArgumentException("TCP 服务器端口无效"); } - // 2. 创建 TCP 客户端 + // 2.1 创建 TCP 客户端 IotTcpClient tcpClient = new IotTcpClient( config.getHost(), config.getPort(), @@ -51,13 +51,10 @@ public class IotTcpDataRuleAction extends config.getSslCertPath(), config.getDataFormat() ); - - // 3. 连接服务器 + // 2.2 连接服务器 tcpClient.connect(); - log.info("[initProducer][TCP 客户端创建并连接成功,服务器: {}:{},SSL: {},数据格式: {}]", config.getHost(), config.getPort(), config.getSsl(), config.getDataFormat()); - return tcpClient; } @@ -71,22 +68,19 @@ public class IotTcpDataRuleAction extends @Override protected void execute(IotDeviceMessage message, IotDataSinkTcpConfig config) throws Exception { try { - // 1. 获取或创建 TCP 客户端 + // 1.1 获取或创建 TCP 客户端 IotTcpClient tcpClient = getProducer(config); - - // 2. 检查连接状态,如果断开则重新连接 + // 1.2 检查连接状态,如果断开则重新连接 if (!tcpClient.isConnected()) { log.warn("[execute][TCP 连接已断开,尝试重新连接,服务器: {}:{}]", config.getHost(), config.getPort()); tcpClient.connect(); } - // 3. 发送消息并等待结果 + // 2.1 发送消息并等待结果 tcpClient.sendMessage(message); - - // 4. 记录发送成功日志 + // 2.2 记录发送成功日志 log.info("[execute][message({}) config({}) 发送成功,TCP 服务器: {}:{}]", message, config, config.getHost(), config.getPort()); - } catch (Exception e) { log.error("[execute][message({}) config({}) 发送失败,TCP 服务器: {}:{}]", message, config, config.getHost(), config.getPort(), e); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/tcp/IotTcpClient.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/tcp/IotTcpClient.java index 052c43ed4c..1618532a4a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/tcp/IotTcpClient.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/tcp/IotTcpClient.java @@ -38,6 +38,7 @@ public class IotTcpClient { private BufferedReader reader; private final AtomicBoolean connected = new AtomicBoolean(false); + // TODO @puhui999:default 值,IotDataSinkTcpConfig.java 枚举起来哈; public IotTcpClient(String host, Integer port, Integer connectTimeoutMs, Integer readTimeoutMs, Boolean ssl, String sslCertPath, String dataFormat) { this.host = host; @@ -76,9 +77,9 @@ public class IotTcpClient { outputStream = socket.getOutputStream(); reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)); + // 更新状态 connected.set(true); log.info("[connect][TCP 客户端连接成功,服务器地址: {}:{}]", host, port); - } catch (Exception e) { close(); log.error("[connect][TCP 客户端连接失败,服务器地址: {}:{}]", host, port, e); @@ -98,6 +99,7 @@ public class IotTcpClient { } try { + // TODO @puhui999:枚举值 String messageData; if ("JSON".equalsIgnoreCase(dataFormat)) { // JSON 格式 @@ -111,10 +113,8 @@ public class IotTcpClient { outputStream.write(messageData.getBytes(StandardCharsets.UTF_8)); outputStream.write('\n'); // 添加换行符作为消息分隔符 outputStream.flush(); - log.debug("[sendMessage][发送消息成功,设备 ID: {},消息长度: {}]", message.getDeviceId(), messageData.length()); - } catch (Exception e) { log.error("[sendMessage][发送消息失败,设备 ID: {}]", message.getDeviceId(), e); throw e; @@ -153,9 +153,9 @@ public class IotTcpClient { } } + // 更新状态 connected.set(false); log.info("[close][TCP 客户端连接已关闭,服务器地址: {}:{}]", host, port); - } catch (Exception e) { log.error("[close][关闭 TCP 客户端连接异常]", e); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleActionTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleActionTest.java index 3a7ee1cf73..e37af78333 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleActionTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotTcpDataRuleActionTest.java @@ -25,13 +25,13 @@ class IotTcpDataRuleActionTest { private IotTcpClient mockTcpClient; @BeforeEach - void setUp() { + public void setUp() { MockitoAnnotations.openMocks(this); tcpDataRuleAction = new IotTcpDataRuleAction(); } @Test - void testGetType() { + public void testGetType() { // 准备参数 Integer expectedType = 2; // 数据接收类型枚举中 TCP 类型的值 @@ -42,8 +42,9 @@ class IotTcpDataRuleActionTest { assertEquals(expectedType, actualType); } + // TODO @puhui999:_ 后面是小写哈,单测的命名规则。 @Test - void testInitProducer_Success() throws Exception { + public void testInitProducer_Success() throws Exception { // 准备参数 IotDataSinkTcpConfig config = new IotDataSinkTcpConfig(); config.setHost("localhost"); @@ -59,7 +60,7 @@ class IotTcpDataRuleActionTest { } @Test - void testInitProducer_InvalidHost() { + public void testInitProducer_InvalidHost() { // 准备参数 IotDataSinkTcpConfig config = new IotDataSinkTcpConfig(); config.setHost(""); @@ -77,7 +78,7 @@ class IotTcpDataRuleActionTest { } @Test - void testInitProducer_InvalidPort() { + public void testInitProducer_InvalidPort() { // 准备参数 IotDataSinkTcpConfig config = new IotDataSinkTcpConfig(); config.setHost("localhost"); @@ -92,7 +93,7 @@ class IotTcpDataRuleActionTest { } @Test - void testCloseProducer() throws Exception { + public void testCloseProducer() throws Exception { // 准备参数 IotTcpClient client = mock(IotTcpClient.class); @@ -104,7 +105,7 @@ class IotTcpDataRuleActionTest { } @Test - void testExecute_WithValidConfig() { + public void testExecute_WithValidConfig() { // 准备参数 IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.report", "{\"temperature\": 25.5, \"humidity\": 60}"); @@ -124,7 +125,7 @@ class IotTcpDataRuleActionTest { } @Test - void testConfig_DefaultValues() { + public void testConfig_DefaultValues() { // 准备参数 IotDataSinkTcpConfig config = new IotDataSinkTcpConfig(); @@ -140,7 +141,7 @@ class IotTcpDataRuleActionTest { } @Test - void testMessageSerialization() { + public void testMessageSerialization() { // 准备参数 IotDeviceMessage message = IotDeviceMessage.builder() .deviceId(123L) From 341e72c0b180611abaa1aec496ca90d4186b27cc Mon Sep 17 00:00:00 2001 From: haohao <1036606149@qq.com> Date: Mon, 22 Sep 2025 23:10:26 +0800 Subject: [PATCH 10/11] =?UTF-8?q?feat:=20=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E6=96=B0=E5=A2=9E=E7=AE=80=E5=8D=95=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=A4=84=E7=90=86=E5=99=A8=E9=85=8D=E7=BD=AE=E5=92=8C?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=99=A8=EF=BC=8C=E4=BC=98=E5=8C=96=20JSON?= =?UTF-8?q?=20=E8=A7=A3=E6=9E=90=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/SimpleTypeHandlerConfig.java | 33 ++++++++++++++++ .../handler/SimpleObjectTypeHandler.java | 39 +++++++++++++++++++ .../mapper/device/IotDevicePropertyMapper.xml | 9 ++++- .../mqtt/router/IotMqttUpstreamHandler.java | 20 ++++++---- .../src/main/resources/application.yaml | 1 - 5 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mybatis/config/SimpleTypeHandlerConfig.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mybatis/handler/SimpleObjectTypeHandler.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mybatis/config/SimpleTypeHandlerConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mybatis/config/SimpleTypeHandlerConfig.java new file mode 100644 index 0000000000..7360e488ff --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mybatis/config/SimpleTypeHandlerConfig.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.iot.framework.mybatis.config; + +import cn.iocoder.yudao.module.iot.framework.mybatis.handler.SimpleObjectTypeHandler; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.type.TypeHandlerRegistry; +import org.springframework.context.annotation.Configuration; + +/** + * 简单类型处理器配置 + * 注册自定义的类型处理器,避免 JSON 解析错误 + * + * @author 芋道源码 + */ +@Slf4j +@Configuration +public class SimpleTypeHandlerConfig { + + @Resource + private SqlSessionFactory sqlSessionFactory; + + @PostConstruct + public void registerTypeHandlers() { + TypeHandlerRegistry registry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry(); + + // 注册简单的 Object 类型处理器,避免 JSON 解析问题 + registry.register(java.lang.Object.class, new SimpleObjectTypeHandler()); + + log.info("简单类型处理器注册完成,避免 JSON 解析错误"); + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mybatis/handler/SimpleObjectTypeHandler.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mybatis/handler/SimpleObjectTypeHandler.java new file mode 100644 index 0000000000..c61bf94258 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mybatis/handler/SimpleObjectTypeHandler.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.iot.framework.mybatis.handler; + +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * 简单的 Object 类型处理器 + * 直接返回字符串,避免 JSON 解析问题 + * + * @author 芋道源码 + */ +public class SimpleObjectTypeHandler extends BaseTypeHandler { + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) + throws SQLException { + ps.setString(i, parameter.toString()); + } + + @Override + public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { + return rs.getString(columnName); // 直接返回字符串 + } + + @Override + public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + return rs.getString(columnIndex); // 直接返回字符串 + } + + @Override + public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + return cs.getString(columnIndex); // 直接返回字符串 + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDevicePropertyMapper.xml b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDevicePropertyMapper.xml index fc2d3662fe..85149d67a1 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDevicePropertyMapper.xml +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDevicePropertyMapper.xml @@ -66,8 +66,15 @@ DESCRIBE product_property_${productId} + + + + + - - - - -