From 2e5aa3d6ec80198e3ed06875a71948d768d4630a Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 11 Aug 2025 10:48:29 +0800 Subject: [PATCH 1/8] =?UTF-8?q?style:=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E6=B5=8B=E8=AF=95=20sql=20=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/resources/sql/clean.sql | 32 +- .../src/test/resources/sql/create_tables.sql | 473 ++++-------------- 2 files changed, 101 insertions(+), 404 deletions(-) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/clean.sql b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/clean.sql index 1130e819df..ae1c5e5156 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/clean.sql +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/clean.sql @@ -1,22 +1,10 @@ --- TODO @puhui999:sql 格式 --- IoT 模块测试数据清理脚本 -DELETE -FROM "iot_scene_rule"; -DELETE -FROM "iot_product"; -DELETE -FROM "iot_device"; -DELETE -FROM "iot_thing_model"; -DELETE -FROM "iot_device_data"; -DELETE -FROM "iot_alert_config"; -DELETE -FROM "iot_alert_record"; -DELETE -FROM "iot_ota_firmware"; -DELETE -FROM "iot_ota_task"; -DELETE -FROM "iot_ota_record"; +DELETE FROM "iot_scene_rule"; +DELETE FROM "iot_product"; +DELETE FROM "iot_device"; +DELETE FROM "iot_thing_model"; +DELETE FROM "iot_device_data"; +DELETE FROM "iot_alert_config"; +DELETE FROM "iot_alert_record"; +DELETE FROM "iot_ota_firmware"; +DELETE FROM "iot_ota_task"; +DELETE FROM "iot_ota_record"; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql index 68b1676466..306c66b5e5 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql @@ -1,300 +1,115 @@ --- TODO @puhui999:sql 格式 --- IoT 模块测试数据库表结构 --- 基于 H2 数据库语法,兼容 MySQL 模式 - --- IoT 场景联动规则表 -CREATE TABLE IF NOT EXISTS "iot_scene_rule" -( - "id" - bigint - NOT - NULL - GENERATED - BY - DEFAULT AS - IDENTITY, - "name" - varchar -( - 255 -) NOT NULL DEFAULT '', - "description" varchar -( - 500 -) DEFAULT NULL, +CREATE TABLE IF NOT EXISTS "iot_scene_rule" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar(255) NOT NULL DEFAULT '', + "description" varchar(500) DEFAULT NULL, "status" tinyint NOT NULL DEFAULT '0', "triggers" text, "actions" text, - "creator" varchar -( - 64 -) DEFAULT '', + "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar -( - 64 -) DEFAULT '', + "updater" varchar(64) DEFAULT '', "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY -( - "id" -) - ) COMMENT 'IoT 场景联动规则表'; + PRIMARY KEY ("id") +) COMMENT 'IoT 场景联动规则表'; --- IoT 产品表 -CREATE TABLE IF NOT EXISTS "iot_product" -( - "id" - bigint - NOT - NULL - GENERATED - BY - DEFAULT AS - IDENTITY, - "name" - varchar -( - 255 -) NOT NULL DEFAULT '', - "product_key" varchar -( - 100 -) NOT NULL DEFAULT '', +CREATE TABLE IF NOT EXISTS "iot_product" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar(255) NOT NULL DEFAULT '', + "product_key" varchar(100) NOT NULL DEFAULT '', "protocol_type" tinyint NOT NULL DEFAULT '0', "category_id" bigint DEFAULT NULL, - "description" varchar -( - 500 -) DEFAULT NULL, + "description" varchar(500) DEFAULT NULL, "data_format" tinyint NOT NULL DEFAULT '0', "device_type" tinyint NOT NULL DEFAULT '0', "net_type" tinyint NOT NULL DEFAULT '0', "validate_type" tinyint NOT NULL DEFAULT '0', "status" tinyint NOT NULL DEFAULT '0', - "creator" varchar -( - 64 -) DEFAULT '', + "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar -( - 64 -) DEFAULT '', + "updater" varchar(64) DEFAULT '', "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY -( - "id" -) - ) COMMENT 'IoT 产品表'; + PRIMARY KEY ("id") +) COMMENT 'IoT 产品表'; --- IoT 设备表 -CREATE TABLE IF NOT EXISTS "iot_device" -( - "id" - bigint - NOT - NULL - GENERATED - BY - DEFAULT AS - IDENTITY, - "device_name" - varchar -( - 255 -) NOT NULL DEFAULT '', +CREATE TABLE IF NOT EXISTS "iot_device" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "device_name" varchar(255) NOT NULL DEFAULT '', "product_id" bigint NOT NULL, - "device_key" varchar -( - 100 -) NOT NULL DEFAULT '', - "device_secret" varchar -( - 100 -) NOT NULL DEFAULT '', - "nickname" varchar -( - 255 -) DEFAULT NULL, + "device_key" varchar(100) NOT NULL DEFAULT '', + "device_secret" varchar(100) NOT NULL DEFAULT '', + "nickname" varchar(255) DEFAULT NULL, "status" tinyint NOT NULL DEFAULT '0', "status_last_update_time" timestamp DEFAULT NULL, "last_online_time" timestamp DEFAULT NULL, "last_offline_time" timestamp DEFAULT NULL, "active_time" timestamp DEFAULT NULL, - "ip" varchar -( - 50 -) DEFAULT NULL, - "firmware_version" varchar -( - 50 -) DEFAULT NULL, + "ip" varchar(50) DEFAULT NULL, + "firmware_version" varchar(50) DEFAULT NULL, "device_type" tinyint NOT NULL DEFAULT '0', "gateway_id" bigint DEFAULT NULL, "sub_device_count" int NOT NULL DEFAULT '0', - "creator" varchar -( - 64 -) DEFAULT '', + "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar -( - 64 -) DEFAULT '', + "updater" varchar(64) DEFAULT '', "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY -( - "id" -) - ) COMMENT 'IoT 设备表'; + PRIMARY KEY ("id") +) COMMENT 'IoT 设备表'; --- IoT 物模型表 -CREATE TABLE IF NOT EXISTS "iot_thing_model" -( - "id" - bigint - NOT - NULL - GENERATED - BY - DEFAULT AS - IDENTITY, - "product_id" - bigint - NOT - NULL, - "identifier" - varchar -( - 100 -) NOT NULL DEFAULT '', - "name" varchar -( - 255 -) NOT NULL DEFAULT '', - "description" varchar -( - 500 -) DEFAULT NULL, +CREATE TABLE IF NOT EXISTS "iot_thing_model" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "product_id" bigint NOT NULL, + "identifier" varchar(100) NOT NULL DEFAULT '', + "name" varchar(255) NOT NULL DEFAULT '', + "description" varchar(500) DEFAULT NULL, "type" tinyint NOT NULL DEFAULT '1', "property" text, - "creator" varchar -( - 64 -) DEFAULT '', + "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar -( - 64 -) DEFAULT '', + "updater" varchar(64) DEFAULT '', "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY -( - "id" -) - ) COMMENT 'IoT 物模型表'; + PRIMARY KEY ("id") +) COMMENT 'IoT 物模型表'; --- IoT 设备数据表 -CREATE TABLE IF NOT EXISTS "iot_device_data" -( - "id" - bigint - NOT - NULL - GENERATED - BY - DEFAULT AS - IDENTITY, - "device_id" - bigint - NOT - NULL, - "product_id" - bigint - NOT - NULL, - "identifier" - varchar -( - 100 -) NOT NULL DEFAULT '', +CREATE TABLE IF NOT EXISTS "iot_device_data" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "device_id" bigint NOT NULL, + "product_id" bigint NOT NULL, + "identifier" varchar(100) NOT NULL DEFAULT '', "type" tinyint NOT NULL DEFAULT '1', "data" text, "ts" bigint NOT NULL DEFAULT '0', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY -( - "id" -) - ) COMMENT 'IoT 设备数据表'; + PRIMARY KEY ("id") +) COMMENT 'IoT 设备数据表'; --- IoT 告警配置表 -CREATE TABLE IF NOT EXISTS "iot_alert_config" -( - "id" - bigint - NOT - NULL - GENERATED - BY - DEFAULT AS - IDENTITY, - "name" - varchar -( - 255 -) NOT NULL DEFAULT '', +CREATE TABLE IF NOT EXISTS "iot_alert_config" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar(255) NOT NULL DEFAULT '', "product_id" bigint NOT NULL, "device_id" bigint DEFAULT NULL, "rule_id" bigint DEFAULT NULL, "status" tinyint NOT NULL DEFAULT '0', - "creator" varchar -( - 64 -) DEFAULT '', + "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar -( - 64 -) DEFAULT '', + "updater" varchar(64) DEFAULT '', "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY -( - "id" -) - ) COMMENT 'IoT 告警配置表'; + PRIMARY KEY ("id") +) COMMENT 'IoT 告警配置表'; --- IoT 告警记录表 -CREATE TABLE IF NOT EXISTS "iot_alert_record" -( - "id" - bigint - NOT - NULL - GENERATED - BY - DEFAULT AS - IDENTITY, - "alert_config_id" - bigint - NOT - NULL, - "alert_name" - varchar -( - 255 -) NOT NULL DEFAULT '', +CREATE TABLE IF NOT EXISTS "iot_alert_record" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "alert_config_id" bigint NOT NULL, + "alert_name" varchar(255) NOT NULL DEFAULT '', "product_id" bigint NOT NULL, "device_id" bigint DEFAULT NULL, "rule_id" bigint DEFAULT NULL, @@ -303,171 +118,65 @@ CREATE TABLE IF NOT EXISTS "iot_alert_record" "deal_status" tinyint NOT NULL DEFAULT '0', "deal_time" timestamp DEFAULT NULL, "deal_user_id" bigint DEFAULT NULL, - "deal_remark" varchar -( - 500 -) DEFAULT NULL, - "creator" varchar -( - 64 -) DEFAULT '', + "deal_remark" varchar(500) DEFAULT NULL, + "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar -( - 64 -) DEFAULT '', + "updater" varchar(64) DEFAULT '', "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY -( - "id" -) - ) COMMENT 'IoT 告警记录表'; + PRIMARY KEY ("id") +) COMMENT 'IoT 告警记录表'; --- IoT OTA 固件表 -CREATE TABLE IF NOT EXISTS "iot_ota_firmware" -( - "id" - bigint - NOT - NULL - GENERATED - BY - DEFAULT AS - IDENTITY, - "name" - varchar -( - 255 -) NOT NULL DEFAULT '', +CREATE TABLE IF NOT EXISTS "iot_ota_firmware" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar(255) NOT NULL DEFAULT '', "product_id" bigint NOT NULL, - "version" varchar -( - 50 -) NOT NULL DEFAULT '', - "description" varchar -( - 500 -) DEFAULT NULL, - "file_url" varchar -( - 500 -) DEFAULT NULL, + "version" varchar(50) NOT NULL DEFAULT '', + "description" varchar(500) DEFAULT NULL, + "file_url" varchar(500) DEFAULT NULL, "file_size" bigint NOT NULL DEFAULT '0', "status" tinyint NOT NULL DEFAULT '0', - "creator" varchar -( - 64 -) DEFAULT '', + "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar -( - 64 -) DEFAULT '', + "updater" varchar(64) DEFAULT '', "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY -( - "id" -) - ) COMMENT 'IoT OTA 固件表'; + PRIMARY KEY ("id") +) COMMENT 'IoT OTA 固件表'; --- IoT OTA 升级任务表 -CREATE TABLE IF NOT EXISTS "iot_ota_task" -( - "id" - bigint - NOT - NULL - GENERATED - BY - DEFAULT AS - IDENTITY, - "name" - varchar -( - 255 -) NOT NULL DEFAULT '', +CREATE TABLE IF NOT EXISTS "iot_ota_task" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar(255) NOT NULL DEFAULT '', "firmware_id" bigint NOT NULL, "product_id" bigint NOT NULL, "upgrade_type" tinyint NOT NULL DEFAULT '0', "status" tinyint NOT NULL DEFAULT '0', - "creator" varchar -( - 64 -) DEFAULT '', + "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar -( - 64 -) DEFAULT '', + "updater" varchar(64) DEFAULT '', "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY -( - "id" -) - ) COMMENT 'IoT OTA 升级任务表'; + PRIMARY KEY ("id") +) COMMENT 'IoT OTA 升级任务表'; --- IoT OTA 升级记录表 -CREATE TABLE IF NOT EXISTS "iot_ota_record" -( - "id" - bigint - NOT - NULL - GENERATED - BY - DEFAULT AS - IDENTITY, - "task_id" - bigint - NOT - NULL, - "firmware_id" - bigint - NOT - NULL, - "device_id" - bigint - NOT - NULL, - "status" - tinyint - NOT - NULL - DEFAULT - '0', - "progress" - int - NOT - NULL - DEFAULT - '0', - "error_msg" - varchar -( - 500 -) DEFAULT NULL, +CREATE TABLE IF NOT EXISTS "iot_ota_record" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "task_id" bigint NOT NULL, + "firmware_id" bigint NOT NULL, + "device_id" bigint NOT NULL, + "status" tinyint NOT NULL DEFAULT '0', + "progress" int NOT NULL DEFAULT '0', + "error_msg" varchar(500) DEFAULT NULL, "start_time" timestamp DEFAULT NULL, "end_time" timestamp DEFAULT NULL, - "creator" varchar -( - 64 -) DEFAULT '', + "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updater" varchar -( - 64 -) DEFAULT '', + "updater" varchar(64) DEFAULT '', "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, "tenant_id" bigint NOT NULL DEFAULT '0', - PRIMARY KEY -( - "id" -) - ) COMMENT 'IoT OTA 升级记录表'; + PRIMARY KEY ("id") +) COMMENT 'IoT OTA 升级记录表'; From 4c051620b14a7f9fe4649059516abea91330aafc Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 11 Aug 2025 11:53:35 +0800 Subject: [PATCH 2/8] =?UTF-8?q?refactor:=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E9=87=8D=E5=91=BD=E5=90=8D=20RuleScene=20=3D?= =?UTF-8?q?>=20SceneRule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...oller.java => IotSceneRuleController.java} | 63 +++--- ...eReqVO.java => IotSceneRulePageReqVO.java} | 2 +- ...eneRespVO.java => IotSceneRuleRespVO.java} | 2 +- ...eReqVO.java => IotSceneRuleSaveReqVO.java} | 2 +- ...ava => IotSceneRuleUpdateStatusReqVO.java} | 2 +- .../dal/dataobject/rule/IotSceneRuleDO.java | 48 ++-- ...eneMapper.java => IotSceneRuleMapper.java} | 6 +- ...m.java => IotSceneRuleActionTypeEnum.java} | 4 +- ...=> IotSceneRuleConditionOperatorEnum.java} | 6 +- ...ava => IotSceneRuleConditionTypeEnum.java} | 4 +- ....java => IotSceneRuleTriggerTypeEnum.java} | 4 +- .../module/iot/job/rule/IotRuleSceneJob.java | 58 ----- .../module/iot/job/rule/IotSceneRuleJob.java | 58 +++++ ...r.java => IotSceneRuleMessageHandler.java} | 8 +- .../alert/IotAlertConfigServiceImpl.java | 8 +- ...eService.java => IotSceneRuleService.java} | 34 +-- ...Impl.java => IotSceneRuleServiceImpl.java} | 210 +++++++++--------- .../IotAlertRecoverSceneRuleAction.java | 6 +- .../IotAlertTriggerSceneRuleAction.java | 6 +- ...a => IotDeviceControlSceneRuleAction.java} | 8 +- .../rule/scene/action/IotSceneRuleAction.java | 4 +- ...ava => IotSceneRuleServiceSimpleTest.java} | 114 +++++----- 22 files changed, 328 insertions(+), 329 deletions(-) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/{IotRuleSceneController.java => IotSceneRuleController.java} (55%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/{IotRuleScenePageReqVO.java => IotSceneRulePageReqVO.java} (95%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/{IotRuleSceneRespVO.java => IotSceneRuleRespVO.java} (97%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/{IotRuleSceneSaveReqVO.java => IotSceneRuleSaveReqVO.java} (97%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/{IotRuleSceneUpdateStatusReqVO.java => IotSceneRuleUpdateStatusReqVO.java} (94%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/{IotRuleSceneMapper.java => IotSceneRuleMapper.java} (88%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/{IotRuleSceneActionTypeEnum.java => IotSceneRuleActionTypeEnum.java} (88%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/{IotRuleSceneConditionOperatorEnum.java => IotSceneRuleConditionOperatorEnum.java} (93%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/{IotRuleSceneConditionTypeEnum.java => IotSceneRuleConditionTypeEnum.java} (83%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/{IotRuleSceneTriggerTypeEnum.java => IotSceneRuleTriggerTypeEnum.java} (91%) delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/rule/IotRuleSceneJob.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/rule/IotSceneRuleJob.java rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/{IotRuleSceneMessageHandler.java => IotSceneRuleMessageHandler.java} (82%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/{IotRuleSceneService.java => IotSceneRuleService.java} (68%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/{IotRuleSceneServiceImpl.java => IotSceneRuleServiceImpl.java} (74%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/{IotDeviceControlRuleSceneAction.java => IotDeviceControlSceneRuleAction.java} (90%) rename yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/{IotRuleSceneServiceSimpleTest.java => IotSceneRuleServiceSimpleTest.java} (53%) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotRuleSceneController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotSceneRuleController.java similarity index 55% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotRuleSceneController.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotSceneRuleController.java index fee12bba7d..57d71be82a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotRuleSceneController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotSceneRuleController.java @@ -4,12 +4,12 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleScenePageReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneRespVO; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneSaveReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneUpdateStatusReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleRespVO; +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleUpdateStatusReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.service.rule.scene.IotRuleSceneService; +import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -24,71 +24,70 @@ import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -// TODO @puhui999:SceneRule 方法名,类名等; @Tag(name = "管理后台 - IoT 场景联动") @RestController -@RequestMapping("/iot/rule-scene") +@RequestMapping("/iot/scene-rule") @Validated -public class IotRuleSceneController { +public class IotSceneRuleController { @Resource - private IotRuleSceneService ruleSceneService; + private IotSceneRuleService sceneRuleService; @PostMapping("/create") @Operation(summary = "创建场景联动") - @PreAuthorize("@ss.hasPermission('iot:rule-scene:create')") - public CommonResult createRuleScene(@Valid @RequestBody IotRuleSceneSaveReqVO createReqVO) { - return success(ruleSceneService.createRuleScene(createReqVO)); + @PreAuthorize("@ss.hasPermission('iot:scene-rule:create')") + public CommonResult createSceneRule(@Valid @RequestBody IotSceneRuleSaveReqVO createReqVO) { + return success(sceneRuleService.createSceneRule(createReqVO)); } @PutMapping("/update") @Operation(summary = "更新场景联动") - @PreAuthorize("@ss.hasPermission('iot:rule-scene:update')") - public CommonResult updateRuleScene(@Valid @RequestBody IotRuleSceneSaveReqVO updateReqVO) { - ruleSceneService.updateRuleScene(updateReqVO); + @PreAuthorize("@ss.hasPermission('iot:scene-rule:update')") + public CommonResult updateSceneRule(@Valid @RequestBody IotSceneRuleSaveReqVO updateReqVO) { + sceneRuleService.updateSceneRule(updateReqVO); return success(true); } @PutMapping("/update-status") @Operation(summary = "更新场景联动状态") - @PreAuthorize("@ss.hasPermission('iot:rule-scene:update')") - public CommonResult updateRuleSceneStatus(@Valid @RequestBody IotRuleSceneUpdateStatusReqVO updateReqVO) { - ruleSceneService.updateRuleSceneStatus(updateReqVO.getId(), updateReqVO.getStatus()); + @PreAuthorize("@ss.hasPermission('iot:scene-rule:update')") + public CommonResult updateSceneRuleStatus(@Valid @RequestBody IotSceneRuleUpdateStatusReqVO updateReqVO) { + sceneRuleService.updateSceneRuleStatus(updateReqVO.getId(), updateReqVO.getStatus()); return success(true); } @DeleteMapping("/delete") @Operation(summary = "删除场景联动") @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('iot:rule-scene:delete')") - public CommonResult deleteRuleScene(@RequestParam("id") Long id) { - ruleSceneService.deleteRuleScene(id); + @PreAuthorize("@ss.hasPermission('iot:scene-rule:delete')") + public CommonResult deleteSceneRule(@RequestParam("id") Long id) { + sceneRuleService.deleteSceneRule(id); return success(true); } @GetMapping("/get") @Operation(summary = "获得场景联动") @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('iot:rule-scene:query')") - public CommonResult getRuleScene(@RequestParam("id") Long id) { - IotSceneRuleDO ruleScene = ruleSceneService.getRuleScene(id); - return success(BeanUtils.toBean(ruleScene, IotRuleSceneRespVO.class)); + @PreAuthorize("@ss.hasPermission('iot:scene-rule:query')") + public CommonResult getSceneRule(@RequestParam("id") Long id) { + IotSceneRuleDO sceneRule = sceneRuleService.getSceneRule(id); + return success(BeanUtils.toBean(sceneRule, IotSceneRuleRespVO.class)); } @GetMapping("/page") @Operation(summary = "获得场景联动分页") - @PreAuthorize("@ss.hasPermission('iot:rule-scene:query')") - public CommonResult> getRuleScenePage(@Valid IotRuleScenePageReqVO pageReqVO) { - PageResult pageResult = ruleSceneService.getRuleScenePage(pageReqVO); - return success(BeanUtils.toBean(pageResult, IotRuleSceneRespVO.class)); + @PreAuthorize("@ss.hasPermission('iot:scene-rule:query')") + public CommonResult> getSceneRulePage(@Valid IotSceneRulePageReqVO pageReqVO) { + PageResult pageResult = sceneRuleService.getSceneRulePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, IotSceneRuleRespVO.class)); } @GetMapping("/simple-list") @Operation(summary = "获取场景联动的精简信息列表", description = "主要用于前端的下拉选项") - public CommonResult> getRuleSceneSimpleList() { - List list = ruleSceneService.getRuleSceneListByStatus(CommonStatusEnum.ENABLE.getStatus()); + public CommonResult> getSceneRuleSimpleList() { + List list = sceneRuleService.getSceneRuleListByStatus(CommonStatusEnum.ENABLE.getStatus()); return success(convertList(list, scene -> // 只返回 id、name 字段 - new IotRuleSceneRespVO().setId(scene.getId()).setName(scene.getName()))); + new IotSceneRuleRespVO().setId(scene.getId()).setName(scene.getName()))); } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleScenePageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRulePageReqVO.java similarity index 95% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleScenePageReqVO.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRulePageReqVO.java index 66e75b42a8..8345004b67 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleScenePageReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRulePageReqVO.java @@ -17,7 +17,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_ @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class IotRuleScenePageReqVO extends PageParam { +public class IotSceneRulePageReqVO extends PageParam { @Schema(description = "场景名称", example = "赵六") private String name; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleSceneRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java similarity index 97% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleSceneRespVO.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java index c42d9ffe64..835ef62933 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleSceneRespVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java @@ -9,7 +9,7 @@ import java.util.List; @Schema(description = "管理后台 - IoT 场景联动 Response VO") @Data -public class IotRuleSceneRespVO { +public class IotSceneRuleRespVO { @Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865") private Long id; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleSceneSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleSaveReqVO.java similarity index 97% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleSceneSaveReqVO.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleSaveReqVO.java index e6d9c06a57..4a5f1ed9fa 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleSceneSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleSaveReqVO.java @@ -12,7 +12,7 @@ import java.util.List; @Schema(description = "管理后台 - IoT 场景联动新增/修改 Request VO") @Data -public class IotRuleSceneSaveReqVO { +public class IotSceneRuleSaveReqVO { @Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865") private Long id; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleSceneUpdateStatusReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleUpdateStatusReqVO.java similarity index 94% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleSceneUpdateStatusReqVO.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleUpdateStatusReqVO.java index 9c98fa0643..ea3721fdd9 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleSceneUpdateStatusReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleUpdateStatusReqVO.java @@ -8,7 +8,7 @@ import lombok.Data; @Schema(description = "管理后台 - IoT 场景联动更新状态 Request VO") @Data -public class IotRuleSceneUpdateStatusReqVO { +public class IotSceneRuleUpdateStatusReqVO { @Schema(description = "场景联动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @NotNull(message = "场景联动编号不能为空") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotSceneRuleDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotSceneRuleDO.java index d1bce72dc7..94aa1eb5a3 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotSceneRuleDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotSceneRuleDO.java @@ -7,10 +7,10 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO; 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.thingmodel.IotThingModelDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneConditionOperatorEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneConditionTypeEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; @@ -79,13 +79,13 @@ public class IotSceneRuleDO extends TenantBaseDO { /** * 场景事件类型 * - * 枚举 {@link IotRuleSceneTriggerTypeEnum} - * 1. {@link IotRuleSceneTriggerTypeEnum#DEVICE_STATE_UPDATE} 时,operator 非空,并且 value 为在线状态 - * 2. {@link IotRuleSceneTriggerTypeEnum#DEVICE_PROPERTY_POST} - * {@link IotRuleSceneTriggerTypeEnum#DEVICE_EVENT_POST} 时,identifier、operator 非空,并且 value 为属性值 - * 3. {@link IotRuleSceneTriggerTypeEnum#DEVICE_EVENT_POST} - * {@link IotRuleSceneTriggerTypeEnum#DEVICE_SERVICE_INVOKE} 时,identifier 非空,但是 operator、value 为空 - * 4. {@link IotRuleSceneTriggerTypeEnum#TIMER} 时,conditions 非空,并且设备无关(无需 productId、deviceId 字段) + * 枚举 {@link IotSceneRuleTriggerTypeEnum} + * 1. {@link IotSceneRuleTriggerTypeEnum#DEVICE_STATE_UPDATE} 时,operator 非空,并且 value 为在线状态 + * 2. {@link IotSceneRuleTriggerTypeEnum#DEVICE_PROPERTY_POST} + * {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST} 时,identifier、operator 非空,并且 value 为属性值 + * 3. {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST} + * {@link IotSceneRuleTriggerTypeEnum#DEVICE_SERVICE_INVOKE} 时,identifier 非空,但是 operator、value 为空 + * 4. {@link IotSceneRuleTriggerTypeEnum#TIMER} 时,conditions 非空,并且设备无关(无需 productId、deviceId 字段) */ private Integer type; @@ -111,14 +111,14 @@ public class IotSceneRuleDO extends TenantBaseDO { /** * 操作符 * - * 枚举 {@link IotRuleSceneConditionOperatorEnum} + * 枚举 {@link IotSceneRuleConditionOperatorEnum} */ private String operator; /** * 参数(属性值、在线状态) *

* 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。 - * 例如说,{@link IotRuleSceneConditionOperatorEnum#IN}、{@link IotRuleSceneConditionOperatorEnum#BETWEEN} + * 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN} */ private String value; @@ -148,10 +148,10 @@ public class IotSceneRuleDO extends TenantBaseDO { /** * 触发条件类型 * - * 枚举 {@link IotRuleSceneConditionTypeEnum} - * 1. {@link IotRuleSceneConditionTypeEnum#DEVICE_STATE} 时,operator 非空,并且 value 为在线状态 - * 2. {@link IotRuleSceneConditionTypeEnum#DEVICE_PROPERTY} 时,identifier、operator 非空,并且 value 为属性值 - * 3. {@link IotRuleSceneConditionTypeEnum#CURRENT_TIME} 时,operator 非空(使用 DATE_TIME_ 和 TIME_ 部分),并且 value 非空 + * 枚举 {@link IotSceneRuleConditionTypeEnum} + * 1. {@link IotSceneRuleConditionTypeEnum#DEVICE_STATE} 时,operator 非空,并且 value 为在线状态 + * 2. {@link IotSceneRuleConditionTypeEnum#DEVICE_PROPERTY} 时,identifier、operator 非空,并且 value 为属性值 + * 3. {@link IotSceneRuleConditionTypeEnum#CURRENT_TIME} 时,operator 非空(使用 DATE_TIME_ 和 TIME_ 部分),并且 value 非空 */ private Integer type; @@ -176,14 +176,14 @@ public class IotSceneRuleDO extends TenantBaseDO { /** * 操作符 * - * 枚举 {@link IotRuleSceneConditionOperatorEnum} + * 枚举 {@link IotSceneRuleConditionOperatorEnum} */ private String operator; /** * 参数 * * 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。 - * 例如说,{@link IotRuleSceneConditionOperatorEnum#IN}、{@link IotRuleSceneConditionOperatorEnum#BETWEEN} + * 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN} */ private String param; @@ -198,11 +198,11 @@ public class IotSceneRuleDO extends TenantBaseDO { /** * 执行类型 * - * 枚举 {@link IotRuleSceneActionTypeEnum} - * 1. {@link IotRuleSceneActionTypeEnum#DEVICE_PROPERTY_SET} 时,params 非空 - * {@link IotRuleSceneActionTypeEnum#DEVICE_SERVICE_INVOKE} 时,params 非空 - * 2. {@link IotRuleSceneActionTypeEnum#ALERT_TRIGGER} 时,alertConfigId 为空,因为是 {@link IotAlertConfigDO} 里面关联它 - * 3. {@link IotRuleSceneActionTypeEnum#ALERT_RECOVER} 时,alertConfigId 非空 + * 枚举 {@link IotSceneRuleActionTypeEnum} + * 1. {@link IotSceneRuleActionTypeEnum#DEVICE_PROPERTY_SET} 时,params 非空 + * {@link IotSceneRuleActionTypeEnum#DEVICE_SERVICE_INVOKE} 时,params 非空 + * 2. {@link IotSceneRuleActionTypeEnum#ALERT_TRIGGER} 时,alertConfigId 为空,因为是 {@link IotAlertConfigDO} 里面关联它 + * 3. {@link IotSceneRuleActionTypeEnum#ALERT_RECOVER} 时,alertConfigId 非空 */ private Integer type; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotRuleSceneMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotSceneRuleMapper.java similarity index 88% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotRuleSceneMapper.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotSceneRuleMapper.java index 9294366109..4fd6490d15 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotRuleSceneMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotSceneRuleMapper.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.dal.mysql.rule; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleScenePageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import org.apache.ibatis.annotations.Mapper; @@ -15,9 +15,9 @@ import java.util.List; * @author HUIHUI */ @Mapper -public interface IotRuleSceneMapper extends BaseMapperX { +public interface IotSceneRuleMapper extends BaseMapperX { - default PageResult selectPage(IotRuleScenePageReqVO reqVO) { + default PageResult selectPage(IotSceneRulePageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .likeIfPresent(IotSceneRuleDO::getName, reqVO.getName()) .likeIfPresent(IotSceneRuleDO::getDescription, reqVO.getDescription()) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneActionTypeEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleActionTypeEnum.java similarity index 88% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneActionTypeEnum.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleActionTypeEnum.java index 323592b26b..7e9e4de631 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneActionTypeEnum.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleActionTypeEnum.java @@ -14,7 +14,7 @@ import java.util.Arrays; */ @RequiredArgsConstructor @Getter -public enum IotRuleSceneActionTypeEnum implements ArrayValuable { +public enum IotSceneRuleActionTypeEnum implements ArrayValuable { /** * 设备属性设置 @@ -42,7 +42,7 @@ public enum IotRuleSceneActionTypeEnum implements ArrayValuable { private final Integer type; - public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneActionTypeEnum::getType).toArray(Integer[]::new); + public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleActionTypeEnum::getType).toArray(Integer[]::new); @Override public Integer[] array() { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneConditionOperatorEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionOperatorEnum.java similarity index 93% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneConditionOperatorEnum.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionOperatorEnum.java index f9debc9ca9..9bf90cff62 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneConditionOperatorEnum.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionOperatorEnum.java @@ -14,7 +14,7 @@ import java.util.Arrays; */ @RequiredArgsConstructor @Getter -public enum IotRuleSceneConditionOperatorEnum implements ArrayValuable { +public enum IotSceneRuleConditionOperatorEnum implements ArrayValuable { EQUALS("=", "#source == #value"), NOT_EQUALS("!=", "!(#source == #value)"), @@ -53,7 +53,7 @@ public enum IotRuleSceneConditionOperatorEnum implements ArrayValuable { private final String operator; private final String springExpression; - public static final String[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneConditionOperatorEnum::getOperator).toArray(String[]::new); + public static final String[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleConditionOperatorEnum::getOperator).toArray(String[]::new); /** * Spring 表达式 - 原始值 @@ -68,7 +68,7 @@ public enum IotRuleSceneConditionOperatorEnum implements ArrayValuable { */ public static final String SPRING_EXPRESSION_VALUE_LIST = "values"; - public static IotRuleSceneConditionOperatorEnum operatorOf(String operator) { + public static IotSceneRuleConditionOperatorEnum operatorOf(String operator) { return ArrayUtil.firstMatch(item -> item.getOperator().equals(operator), values()); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneConditionTypeEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionTypeEnum.java similarity index 83% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneConditionTypeEnum.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionTypeEnum.java index 031976dc60..69cd589e45 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneConditionTypeEnum.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionTypeEnum.java @@ -13,7 +13,7 @@ import java.util.Arrays; */ @RequiredArgsConstructor @Getter -public enum IotRuleSceneConditionTypeEnum implements ArrayValuable { +public enum IotSceneRuleConditionTypeEnum implements ArrayValuable { DEVICE_STATE(1, "设备状态"), DEVICE_PROPERTY(2, "设备属性"), @@ -25,7 +25,7 @@ public enum IotRuleSceneConditionTypeEnum implements ArrayValuable { private final Integer type; private final String name; - public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneConditionTypeEnum::getType).toArray(Integer[]::new); + public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleConditionTypeEnum::getType).toArray(Integer[]::new); @Override public Integer[] array() { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneTriggerTypeEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java similarity index 91% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneTriggerTypeEnum.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java index 565ac402cc..5e502b59d4 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneTriggerTypeEnum.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java @@ -16,7 +16,7 @@ import java.util.Arrays; */ @RequiredArgsConstructor @Getter -public enum IotRuleSceneTriggerTypeEnum implements ArrayValuable { +public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable { @Deprecated DEVICE(1), // 设备触发 // TODO @puhui999:@芋艿:这个可以作废 @@ -56,7 +56,7 @@ public enum IotRuleSceneTriggerTypeEnum implements ArrayValuable { private final Integer type; - public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneTriggerTypeEnum::getType).toArray(Integer[]::new); + public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleTriggerTypeEnum::getType).toArray(Integer[]::new); @Override public Integer[] array() { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/rule/IotRuleSceneJob.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/rule/IotRuleSceneJob.java deleted file mode 100644 index 352162e188..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/rule/IotRuleSceneJob.java +++ /dev/null @@ -1,58 +0,0 @@ -package cn.iocoder.yudao.module.iot.job.rule; - -import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.IotRuleSceneService; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.quartz.JobExecutionContext; -import org.springframework.scheduling.quartz.QuartzJobBean; - -import java.util.Map; - -/** - * IoT 规则场景 Job,用于执行 {@link IotRuleSceneTriggerTypeEnum#TIMER} 类型的规则场景 - * - * @author 芋道源码 - */ -@Slf4j -public class IotRuleSceneJob extends QuartzJobBean { - - /** - * JobData Key - 规则场景编号 - */ - public static final String JOB_DATA_KEY_RULE_SCENE_ID = "ruleSceneId"; - - @Resource - private IotRuleSceneService ruleSceneService; - - @Override - protected void executeInternal(JobExecutionContext context) { - // 获得规则场景编号 - Long ruleSceneId = context.getMergedJobDataMap().getLong(JOB_DATA_KEY_RULE_SCENE_ID); - - // 执行规则场景 - ruleSceneService.executeRuleSceneByTimer(ruleSceneId); - } - - /** - * 创建 JobData Map - * - * @param ruleSceneId 规则场景编号 - * @return JobData Map - */ - public static Map buildJobDataMap(Long ruleSceneId) { - return MapUtil.of(JOB_DATA_KEY_RULE_SCENE_ID, ruleSceneId); - } - - /** - * 创建 Job 名字 - * - * @param ruleSceneId 规则场景编号 - * @return Job 名字 - */ - public static String buildJobName(Long ruleSceneId) { - return String.format("%s_%d", IotRuleSceneJob.class.getSimpleName(), ruleSceneId); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/rule/IotSceneRuleJob.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/rule/IotSceneRuleJob.java new file mode 100644 index 0000000000..9967ccc3b1 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/rule/IotSceneRuleJob.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.iot.job.rule; + +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.quartz.JobExecutionContext; +import org.springframework.scheduling.quartz.QuartzJobBean; + +import java.util.Map; + +/** + * IoT 规则场景 Job,用于执行 {@link IotSceneRuleTriggerTypeEnum#TIMER} 类型的规则场景 + * + * @author 芋道源码 + */ +@Slf4j +public class IotSceneRuleJob extends QuartzJobBean { + + /** + * JobData Key - 规则场景编号 + */ + public static final String JOB_DATA_KEY_RULE_SCENE_ID = "sceneRuleId"; + + @Resource + private IotSceneRuleService sceneRuleService; + + @Override + protected void executeInternal(JobExecutionContext context) { + // 获得规则场景编号 + Long sceneRuleId = context.getMergedJobDataMap().getLong(JOB_DATA_KEY_RULE_SCENE_ID); + + // 执行规则场景 + sceneRuleService.executeSceneRuleByTimer(sceneRuleId); + } + + /** + * 创建 JobData Map + * + * @param sceneRuleId 规则场景编号 + * @return JobData Map + */ + public static Map buildJobDataMap(Long sceneRuleId) { + return MapUtil.of(JOB_DATA_KEY_RULE_SCENE_ID, sceneRuleId); + } + + /** + * 创建 Job 名字 + * + * @param sceneRuleId 规则场景编号 + * @return Job 名字 + */ + public static String buildJobName(Long sceneRuleId) { + return String.format("%s_%d", IotSceneRuleJob.class.getSimpleName(), sceneRuleId); + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotRuleSceneMessageHandler.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java similarity index 82% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotRuleSceneMessageHandler.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java index 4212a78a47..c39cefe4ab 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotRuleSceneMessageHandler.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.mq.consumer.rule; import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.service.rule.scene.IotRuleSceneService; +import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -17,10 +17,10 @@ import org.springframework.stereotype.Component; */ @Component @Slf4j -public class IotRuleSceneMessageHandler implements IotMessageSubscriber { +public class IotSceneRuleMessageHandler implements IotMessageSubscriber { @Resource - private IotRuleSceneService ruleSceneService; + private IotSceneRuleService sceneRuleService; @Resource private IotMessageBus messageBus; @@ -46,7 +46,7 @@ public class IotRuleSceneMessageHandler implements IotMessageSubscriber getRuleScenePage(IotRuleScenePageReqVO pageReqVO); + PageResult getSceneRulePage(IotSceneRulePageReqVO pageReqVO); /** * 校验规则场景联动规则编号们是否存在。如下情况,视为无效: @@ -70,7 +70,7 @@ public interface IotRuleSceneService { * * @param ids 场景联动规则编号数组 */ - void validateRuleSceneList(Collection ids); + void validateSceneRuleList(Collection ids); /** * 获得指定状态的场景联动列表 @@ -78,7 +78,7 @@ public interface IotRuleSceneService { * @param status 状态 * @return 场景联动列表 */ - List getRuleSceneListByStatus(Integer status); + List getSceneRuleListByStatus(Integer status); /** * 【缓存】获得指定设备的场景列表 @@ -87,20 +87,20 @@ public interface IotRuleSceneService { * @param deviceName 设备名称 * @return 场景列表 */ - List getRuleSceneListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName); + List getSceneRuleListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName); /** - * 基于 {@link IotRuleSceneTriggerTypeEnum#DEVICE} 场景,执行规则场景 + * 基于 {@link IotSceneRuleTriggerTypeEnum#DEVICE} 场景,执行规则场景 * * @param message 消息 */ - void executeRuleSceneByDevice(IotDeviceMessage message); + void executeSceneRuleByDevice(IotDeviceMessage message); /** - * 基于 {@link IotRuleSceneTriggerTypeEnum#TIMER} 场景,执行规则场景 + * 基于 {@link IotSceneRuleTriggerTypeEnum#TIMER} 场景,执行规则场景 * * @param id 场景联动规则编号 */ - void executeRuleSceneByTimer(Long id); + void executeSceneRuleByTimer(Long id); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotRuleSceneServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java similarity index 74% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotRuleSceneServiceImpl.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java index 8a03e58cfc..39efa79e48 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotRuleSceneServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java @@ -15,17 +15,17 @@ import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleScenePageReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneSaveReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO; 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.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.IotRuleSceneMapper; -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneConditionOperatorEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneConditionTypeEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +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; @@ -53,13 +53,13 @@ import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.RULE_SCENE_NO @Service @Validated @Slf4j -public class IotRuleSceneServiceImpl implements IotRuleSceneService { +public class IotSceneRuleServiceImpl implements IotSceneRuleService { @Resource - private IotRuleSceneMapper ruleSceneMapper; + private IotSceneRuleMapper sceneRuleMapper; @Resource - private List ruleSceneActions; + private List sceneRuleActions; @Resource(name = "iotSchedulerManager") private IotSchedulerManager schedulerManager; @@ -71,90 +71,90 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { private IotDeviceService deviceService; @Override - public Long createRuleScene(IotRuleSceneSaveReqVO createReqVO) { - IotSceneRuleDO ruleScene = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class); - ruleSceneMapper.insert(ruleScene); - return ruleScene.getId(); + public Long createSceneRule(IotSceneRuleSaveReqVO createReqVO) { + IotSceneRuleDO sceneRule = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class); + sceneRuleMapper.insert(sceneRule); + return sceneRule.getId(); } @Override - public void updateRuleScene(IotRuleSceneSaveReqVO updateReqVO) { + public void updateSceneRule(IotSceneRuleSaveReqVO updateReqVO) { // 校验存在 - validateRuleSceneExists(updateReqVO.getId()); + validateSceneRuleExists(updateReqVO.getId()); // 更新 IotSceneRuleDO updateObj = BeanUtils.toBean(updateReqVO, IotSceneRuleDO.class); - ruleSceneMapper.updateById(updateObj); + sceneRuleMapper.updateById(updateObj); } @Override - public void updateRuleSceneStatus(Long id, Integer status) { + public void updateSceneRuleStatus(Long id, Integer status) { // 校验存在 - validateRuleSceneExists(id); + validateSceneRuleExists(id); // 更新状态 IotSceneRuleDO updateObj = new IotSceneRuleDO().setId(id).setStatus(status); - ruleSceneMapper.updateById(updateObj); + sceneRuleMapper.updateById(updateObj); } @Override - public void deleteRuleScene(Long id) { + public void deleteSceneRule(Long id) { // 校验存在 - validateRuleSceneExists(id); + validateSceneRuleExists(id); // 删除 - ruleSceneMapper.deleteById(id); + sceneRuleMapper.deleteById(id); } - private void validateRuleSceneExists(Long id) { - if (ruleSceneMapper.selectById(id) == null) { + private void validateSceneRuleExists(Long id) { + if (sceneRuleMapper.selectById(id) == null) { throw exception(RULE_SCENE_NOT_EXISTS); } } @Override - public IotSceneRuleDO getRuleScene(Long id) { - return ruleSceneMapper.selectById(id); + public IotSceneRuleDO getSceneRule(Long id) { + return sceneRuleMapper.selectById(id); } @Override - public PageResult getRuleScenePage(IotRuleScenePageReqVO pageReqVO) { - return ruleSceneMapper.selectPage(pageReqVO); + public PageResult getSceneRulePage(IotSceneRulePageReqVO pageReqVO) { + return sceneRuleMapper.selectPage(pageReqVO); } @Override - public void validateRuleSceneList(Collection ids) { + public void validateSceneRuleList(Collection ids) { if (CollUtil.isEmpty(ids)) { return; } // 批量查询存在的规则场景 - List existingScenes = ruleSceneMapper.selectByIds(ids); + List existingScenes = sceneRuleMapper.selectByIds(ids); if (existingScenes.size() != ids.size()) { throw exception(RULE_SCENE_NOT_EXISTS); } } @Override - public List getRuleSceneListByStatus(Integer status) { - return ruleSceneMapper.selectListByStatus(status); + public List getSceneRuleListByStatus(Integer status) { + return sceneRuleMapper.selectListByStatus(status); } // TODO 芋艿,缓存待实现 @Override - @TenantIgnore // 忽略租户隔离:因为 IotRuleSceneMessageHandler 调用时,一般未传递租户,所以需要忽略 - public List getRuleSceneListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) { + @TenantIgnore // 忽略租户隔离:因为 IotSceneRuleMessageHandler 调用时,一般未传递租户,所以需要忽略 + public List getSceneRuleListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) { // TODO @puhui999:一些注释,看看要不要优化下; // 注意:旧的测试代码已删除,因为使用了废弃的数据结构 // 如需测试,请使用上面的新结构测试代码示例 - List list = ruleSceneMapper.selectList(); + List list = sceneRuleMapper.selectList(); // 只返回启用状态的规则场景 List enabledList = filterList(list, - ruleScene -> CommonStatusEnum.ENABLE.getStatus().equals(ruleScene.getStatus())); + sceneRule -> CommonStatusEnum.ENABLE.getStatus().equals(sceneRule.getStatus())); // 根据 productKey 和 deviceName 进行匹配 - return filterList(enabledList, ruleScene -> { - if (CollUtil.isEmpty(ruleScene.getTriggers())) { + return filterList(enabledList, sceneRule -> { + if (CollUtil.isEmpty(sceneRule.getTriggers())) { return false; } - for (IotSceneRuleDO.Trigger trigger : ruleScene.getTriggers()) { + for (IotSceneRuleDO.Trigger trigger : sceneRule.getTriggers()) { // 检查触发器是否匹配指定的产品和设备 if (isMatchProductAndDevice(trigger, productKey, deviceName)) { return true; @@ -210,43 +210,43 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { } @Override - public void executeRuleSceneByDevice(IotDeviceMessage message) { + public void executeSceneRuleByDevice(IotDeviceMessage message) { // TODO @芋艿:这里的 tenantId,通过设备获取; TenantUtils.execute(message.getTenantId(), () -> { // 1. 获得设备匹配的规则场景 - List ruleScenes = getMatchedRuleSceneListByMessage(message); - if (CollUtil.isEmpty(ruleScenes)) { + List sceneRules = getMatchedSceneRuleListByMessage(message); + if (CollUtil.isEmpty(sceneRules)) { return; } // 2. 执行规则场景 - executeRuleSceneAction(message, ruleScenes); + executeSceneRuleAction(message, sceneRules); }); } @Override - public void executeRuleSceneByTimer(Long id) { + public void executeSceneRuleByTimer(Long id) { // 1.1 获得规则场景 - IotSceneRuleDO scene = TenantUtils.executeIgnore(() -> ruleSceneMapper.selectById(id)); + IotSceneRuleDO scene = TenantUtils.executeIgnore(() -> sceneRuleMapper.selectById(id)); if (scene == null) { - log.error("[executeRuleSceneByTimer][规则场景({}) 不存在]", id); + log.error("[executeSceneRuleByTimer][规则场景({}) 不存在]", id); return; } if (CommonStatusEnum.isDisable(scene.getStatus())) { - log.info("[executeRuleSceneByTimer][规则场景({}) 已被禁用]", id); + log.info("[executeSceneRuleByTimer][规则场景({}) 已被禁用]", id); return; } // 1.2 判断是否有定时触发器,避免脏数据 IotSceneRuleDO.Trigger config = CollUtil.findOne(scene.getTriggers(), - trigger -> ObjUtil.equals(trigger.getType(), IotRuleSceneTriggerTypeEnum.TIMER.getType())); + trigger -> ObjUtil.equals(trigger.getType(), IotSceneRuleTriggerTypeEnum.TIMER.getType())); if (config == null) { - log.error("[executeRuleSceneByTimer][规则场景({}) 不存在定时触发器]", scene); + log.error("[executeSceneRuleByTimer][规则场景({}) 不存在定时触发器]", scene); return; } // 2. 执行规则场景 TenantUtils.execute(scene.getTenantId(), - () -> executeRuleSceneAction(null, ListUtil.toList(scene))); + () -> executeSceneRuleAction(null, ListUtil.toList(scene))); } /** @@ -255,36 +255,36 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { * @param message 设备消息 * @return 规则场景列表 */ - private List getMatchedRuleSceneListByMessage(IotDeviceMessage message) { + private List getMatchedSceneRuleListByMessage(IotDeviceMessage message) { // 1. 匹配设备 // TODO @芋艿:可能需要 getSelf(); 缓存 // 1.1 通过 deviceId 获取设备信息 IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId()); if (device == null) { - log.warn("[getMatchedRuleSceneListByMessage][设备({}) 不存在]", message.getDeviceId()); + log.warn("[getMatchedSceneRuleListByMessage][设备({}) 不存在]", message.getDeviceId()); return List.of(); } // 1.2 通过 productId 获取产品信息 IotProductDO product = productService.getProductFromCache(device.getProductId()); if (product == null) { - log.warn("[getMatchedRuleSceneListByMessage][产品({}) 不存在]", device.getProductId()); + log.warn("[getMatchedSceneRuleListByMessage][产品({}) 不存在]", device.getProductId()); return List.of(); } // 1.3 获取匹配的规则场景 - List ruleScenes = getRuleSceneListByProductKeyAndDeviceNameFromCache( + List sceneRules = getSceneRuleListByProductKeyAndDeviceNameFromCache( product.getProductKey(), device.getDeviceName()); - if (CollUtil.isEmpty(ruleScenes)) { - return ruleScenes; + if (CollUtil.isEmpty(sceneRules)) { + return sceneRules; } // 2. 匹配 trigger 触发器的条件 - return filterList(ruleScenes, ruleScene -> { - for (IotSceneRuleDO.Trigger trigger : ruleScene.getTriggers()) { + return filterList(sceneRules, sceneRule -> { + for (IotSceneRuleDO.Trigger trigger : sceneRule.getTriggers()) { // 2.1 检查触发器类型,根据新的枚举值进行匹配 // TODO @芋艿:需要根据新的触发器类型枚举进行适配 - // 原来使用 IotRuleSceneTriggerTypeEnum.DEVICE,新结构可能有不同的类型 + // 原来使用 IotSceneRuleTriggerTypeEnum.DEVICE,新结构可能有不同的类型 // 2.2 条件分组为空,说明没有匹配的条件,因此不匹配 if (CollUtil.isEmpty(trigger.getConditionGroups())) { @@ -303,7 +303,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { for (IotSceneRuleDO.TriggerCondition condition : conditionGroup) { // TODO @芋艿:这里需要实现具体的条件匹配逻辑 // 根据新的 TriggerCondition 结构进行匹配 - if (!isTriggerConditionMatched(message, condition, ruleScene, trigger)) { + if (!isTriggerConditionMatched(message, condition, sceneRule, trigger)) { allConditionsMatched = false; break; } @@ -316,7 +316,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { } if (anyGroupMatched) { - log.info("[getMatchedRuleSceneList][消息({}) 匹配到规则场景编号({}) 的触发器({})]", message, ruleScene.getId(), trigger); + log.info("[getMatchedSceneRuleList][消息({}) 匹配到规则场景编号({}) 的触发器({})]", message, sceneRule.getId(), trigger); return true; } } @@ -329,31 +329,31 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { * * @param message 设备消息 * @param condition 触发条件 - * @param ruleScene 规则场景(用于日志,无其它作用) + * @param sceneRule 规则场景(用于日志,无其它作用) * @param trigger 触发器(用于日志,无其它作用) * @return 是否匹配 */ private boolean isTriggerConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, - IotSceneRuleDO ruleScene, IotSceneRuleDO.Trigger trigger) { + IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) { try { // 1. 根据条件类型进行匹配 - if (IotRuleSceneConditionTypeEnum.DEVICE_STATE.getType().equals(condition.getType())) { + if (IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType().equals(condition.getType())) { // 设备状态条件匹配 return matchDeviceStateCondition(message, condition); - } else if (IotRuleSceneConditionTypeEnum.DEVICE_PROPERTY.getType().equals(condition.getType())) { + } else if (IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType().equals(condition.getType())) { // 设备属性条件匹配 return matchDevicePropertyCondition(message, condition); - } else if (IotRuleSceneConditionTypeEnum.CURRENT_TIME.getType().equals(condition.getType())) { + } else if (IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType().equals(condition.getType())) { // 当前时间条件匹配 return matchCurrentTimeCondition(condition); } else { log.warn("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 存在未知的条件类型({})]", - ruleScene.getId(), trigger, condition.getType()); + sceneRule.getId(), trigger, condition.getType()); return false; } } catch (Exception e) { log.error("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 条件匹配异常]", - ruleScene.getId(), trigger, e); + sceneRule.getId(), trigger, e); return false; } } @@ -420,7 +420,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { private boolean evaluateCondition(Object sourceValue, String operator, String paramValue) { try { // 1. 校验操作符是否合法 - IotRuleSceneConditionOperatorEnum operatorEnum = IotRuleSceneConditionOperatorEnum.operatorOf(operator); + IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); if (operatorEnum == null) { log.warn("[evaluateCondition][存在错误的操作符({})]", operator); return false; @@ -428,22 +428,22 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { // 2.1 构建 Spring 表达式的变量 Map springExpressionVariables = MapUtil.builder() - .put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue) + .put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue) .build(); // 2.2 根据操作符类型处理参数值 if (StrUtil.isNotBlank(paramValue)) { - // TODO @puhui999:这里是不是在 IotRuleSceneConditionOperatorEnum 加个属性; - if (operatorEnum == IotRuleSceneConditionOperatorEnum.IN - || operatorEnum == IotRuleSceneConditionOperatorEnum.NOT_IN - || operatorEnum == IotRuleSceneConditionOperatorEnum.BETWEEN - || operatorEnum == IotRuleSceneConditionOperatorEnum.NOT_BETWEEN) { + // TODO @puhui999:这里是不是在 IotSceneRuleConditionOperatorEnum 加个属性; + if (operatorEnum == IotSceneRuleConditionOperatorEnum.IN + || operatorEnum == IotSceneRuleConditionOperatorEnum.NOT_IN + || operatorEnum == IotSceneRuleConditionOperatorEnum.BETWEEN + || operatorEnum == IotSceneRuleConditionOperatorEnum.NOT_BETWEEN) { // 处理多值情况 List paramValues = StrUtil.split(paramValue, CharPool.COMMA); - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, convertList(paramValues, NumberUtil::parseDouble)); } else { // 处理单值情况 - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE, + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, NumberUtil.parseDouble(paramValue)); } } @@ -464,19 +464,19 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { * * @param message 设备消息 * @param condition 触发条件 - * @param ruleScene 规则场景(用于日志,无其它作用) + * @param sceneRule 规则场景(用于日志,无其它作用) * @param trigger 触发器(用于日志,无其它作用) * @return 是否匹配 */ @SuppressWarnings({"unchecked", "DataFlowIssue"}) private boolean isTriggerConditionParameterMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, - IotSceneRuleDO ruleScene, IotSceneRuleDO.Trigger trigger) { + IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) { // 1.1 校验操作符是否合法 - IotRuleSceneConditionOperatorEnum operator = - IotRuleSceneConditionOperatorEnum.operatorOf(condition.getOperator()); + IotSceneRuleConditionOperatorEnum operator = + IotSceneRuleConditionOperatorEnum.operatorOf(condition.getOperator()); if (operator == null) { log.error("[isTriggerConditionParameterMatched][规则场景编号({}) 的触发器({}) 存在错误的操作符({})]", - ruleScene.getId(), trigger, condition.getOperator()); + sceneRule.getId(), trigger, condition.getOperator()); return false; } // 1.2 校验消息是否包含对应的值 @@ -488,31 +488,31 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { // 2.1 构建 Spring 表达式的变量 Map springExpressionVariables = new HashMap<>(); try { - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, messageValue); - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE, condition.getParam()); + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, messageValue); + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, condition.getParam()); List parameterValues = StrUtil.splitTrim(condition.getParam(), CharPool.COMMA); - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues); + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues); // 特殊:解决数字的比较。因为 Spring 是基于它的 compareTo 方法,对数字的比较存在问题! - if (ObjectUtils.equalsAny(operator, IotRuleSceneConditionOperatorEnum.BETWEEN, - IotRuleSceneConditionOperatorEnum.NOT_BETWEEN, - IotRuleSceneConditionOperatorEnum.GREATER_THAN, - IotRuleSceneConditionOperatorEnum.GREATER_THAN_OR_EQUALS, - IotRuleSceneConditionOperatorEnum.LESS_THAN, - IotRuleSceneConditionOperatorEnum.LESS_THAN_OR_EQUALS) + if (ObjectUtils.equalsAny(operator, IotSceneRuleConditionOperatorEnum.BETWEEN, + IotSceneRuleConditionOperatorEnum.NOT_BETWEEN, + IotSceneRuleConditionOperatorEnum.GREATER_THAN, + IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS, + IotSceneRuleConditionOperatorEnum.LESS_THAN, + IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS) && NumberUtil.isNumber(messageValue) && NumberUtils.isAllNumber(parameterValues)) { - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, NumberUtil.parseDouble(messageValue)); - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE, + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, NumberUtil.parseDouble(condition.getParam())); - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, convertList(parameterValues, NumberUtil::parseDouble)); } // 2.2 计算 Spring 表达式 return (Boolean) SpringExpressionUtils.parseExpression(operator.getSpringExpression(), springExpressionVariables); } catch (Exception e) { log.error("[isTriggerConditionParameterMatched][消息({}) 规则场景编号({}) 的触发器({}) 的匹配表达式({}/{}) 计算异常]", - message, ruleScene.getId(), trigger, operator, springExpressionVariables, e); + message, sceneRule.getId(), trigger, operator, springExpressionVariables, e); return false; } } @@ -521,15 +521,15 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { * 执行规则场景的动作 * * @param message 设备消息 - * @param ruleScenes 规则场景列表 + * @param sceneRules 规则场景列表 */ - private void executeRuleSceneAction(IotDeviceMessage message, List ruleScenes) { + private void executeSceneRuleAction(IotDeviceMessage message, List sceneRules) { // 1. 遍历规则场景 - ruleScenes.forEach(ruleScene -> { + sceneRules.forEach(sceneRule -> { // 2. 遍历规则场景的动作 - ruleScene.getActions().forEach(actionConfig -> { + sceneRule.getActions().forEach(actionConfig -> { // 3.1 获取对应的动作 Action 数组 - List actions = filterList(ruleSceneActions, + List actions = filterList(sceneRuleActions, action -> action.getType().getType().equals(actionConfig.getType())); if (CollUtil.isEmpty(actions)) { return; @@ -537,12 +537,12 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { // 3.2 执行动作 actions.forEach(action -> { try { - action.execute(message, ruleScene, actionConfig); - log.info("[executeRuleSceneAction][消息({}) 规则场景编号({}) 的执行动作({}) 成功]", - message, ruleScene.getId(), actionConfig); + action.execute(message, sceneRule, actionConfig); + log.info("[executeSceneRuleAction][消息({}) 规则场景编号({}) 的执行动作({}) 成功]", + message, sceneRule.getId(), actionConfig); } catch (Exception e) { - log.error("[executeRuleSceneAction][消息({}) 规则场景编号({}) 的执行动作({}) 执行异常]", - message, ruleScene.getId(), actionConfig, e); + log.error("[executeSceneRuleAction][消息({}) 规则场景编号({}) 的执行动作({}) 执行异常]", + message, sceneRule.getId(), actionConfig, e); } }); }); 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 10b93cfec0..851f3815fa 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 @@ -5,7 +5,7 @@ import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum; import cn.iocoder.yudao.module.iot.service.alert.IotAlertRecordService; import jakarta.annotation.Resource; import org.springframework.stereotype.Component; @@ -42,8 +42,8 @@ public class IotAlertRecoverSceneRuleAction implements IotSceneRuleAction { } @Override - public IotRuleSceneActionTypeEnum getType() { - return IotRuleSceneActionTypeEnum.ALERT_RECOVER; + public IotSceneRuleActionTypeEnum getType() { + return IotSceneRuleActionTypeEnum.ALERT_RECOVER; } } 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 a751315265..28223dbd6e 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 @@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum; import cn.iocoder.yudao.module.iot.service.alert.IotAlertConfigService; import cn.iocoder.yudao.module.iot.service.alert.IotAlertRecordService; import cn.iocoder.yudao.module.system.api.mail.MailSendApi; @@ -62,8 +62,8 @@ public class IotAlertTriggerSceneRuleAction implements IotSceneRuleAction { } @Override - public IotRuleSceneActionTypeEnum getType() { - return IotRuleSceneActionTypeEnum.ALERT_TRIGGER; + public IotSceneRuleActionTypeEnum getType() { + return IotSceneRuleActionTypeEnum.ALERT_TRIGGER; } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlRuleSceneAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java similarity index 90% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlRuleSceneAction.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java index 19a7d3cbba..b71a92091b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlRuleSceneAction.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.action; 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.IotRuleSceneActionTypeEnum; +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; @@ -16,7 +16,7 @@ import org.springframework.stereotype.Component; */ @Component @Slf4j -public class IotDeviceControlRuleSceneAction implements IotSceneRuleAction { +public class IotDeviceControlSceneRuleAction implements IotSceneRuleAction { @Resource private IotDeviceService deviceService; @@ -48,8 +48,8 @@ public class IotDeviceControlRuleSceneAction implements IotSceneRuleAction { } @Override - public IotRuleSceneActionTypeEnum getType() { - return IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET; + public IotSceneRuleActionTypeEnum getType() { + return IotSceneRuleActionTypeEnum.DEVICE_PROPERTY_SET; } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotSceneRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotSceneRuleAction.java index 9b5baf6009..c88a37f8ce 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotSceneRuleAction.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotSceneRuleAction.java @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.action; 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.IotRuleSceneActionTypeEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum; import javax.annotation.Nullable; @@ -31,6 +31,6 @@ public interface IotSceneRuleAction { * * @return 类型 */ - IotRuleSceneActionTypeEnum getType(); + IotSceneRuleActionTypeEnum getType(); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotRuleSceneServiceSimpleTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceSimpleTest.java similarity index 53% rename from yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotRuleSceneServiceSimpleTest.java rename to yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceSimpleTest.java index e2735f5bce..056794b797 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotRuleSceneServiceSimpleTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceSimpleTest.java @@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.iot.service.rule.scene; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneSaveReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotRuleSceneMapper; +import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; import cn.iocoder.yudao.module.iot.service.product.IotProductService; @@ -24,21 +24,21 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; /** - * {@link IotRuleSceneServiceImpl} 的简化单元测试类 + * {@link IotSceneRuleServiceImpl} 的简化单元测试类 * 使用 Mockito 进行纯单元测试,不依赖 Spring 容器 * * @author 芋道源码 */ -public class IotRuleSceneServiceSimpleTest extends BaseMockitoUnitTest { +public class IotSceneRuleServiceSimpleTest extends BaseMockitoUnitTest { @InjectMocks - private IotRuleSceneServiceImpl ruleSceneService; + private IotSceneRuleServiceImpl sceneRuleService; @Mock - private IotRuleSceneMapper ruleSceneMapper; + private IotSceneRuleMapper sceneRuleMapper; @Mock - private List ruleSceneActions; + private List sceneRuleActions; @Mock private IotSchedulerManager schedulerManager; @@ -50,9 +50,9 @@ public class IotRuleSceneServiceSimpleTest extends BaseMockitoUnitTest { private IotDeviceService deviceService; @Test - public void testCreateRuleScene_success() { + public void testCreateScene_Rule_success() { // 准备参数 - IotRuleSceneSaveReqVO createReqVO = randomPojo(IotRuleSceneSaveReqVO.class, o -> { + IotSceneRuleSaveReqVO createReqVO = randomPojo(IotSceneRuleSaveReqVO.class, o -> { o.setId(null); o.setStatus(CommonStatusEnum.ENABLE.getStatus()); o.setTriggers(Collections.singletonList(randomPojo(IotSceneRuleDO.Trigger.class))); @@ -61,25 +61,25 @@ public class IotRuleSceneServiceSimpleTest extends BaseMockitoUnitTest { // Mock 行为 Long expectedId = randomLongId(); - when(ruleSceneMapper.insert(any(IotSceneRuleDO.class))).thenAnswer(invocation -> { - IotSceneRuleDO ruleScene = invocation.getArgument(0); - ruleScene.setId(expectedId); + when(sceneRuleMapper.insert(any(IotSceneRuleDO.class))).thenAnswer(invocation -> { + IotSceneRuleDO sceneRule = invocation.getArgument(0); + sceneRule.setId(expectedId); return 1; }); // 调用 - Long ruleSceneId = ruleSceneService.createRuleScene(createReqVO); + Long sceneRuleId = sceneRuleService.createSceneRule(createReqVO); // 断言 - assertEquals(expectedId, ruleSceneId); - verify(ruleSceneMapper, times(1)).insert(any(IotSceneRuleDO.class)); + assertEquals(expectedId, sceneRuleId); + verify(sceneRuleMapper, times(1)).insert(any(IotSceneRuleDO.class)); } @Test - public void testUpdateRuleScene_success() { + public void testUpdateScene_Rule_success() { // 准备参数 Long id = randomLongId(); - IotRuleSceneSaveReqVO updateReqVO = randomPojo(IotRuleSceneSaveReqVO.class, o -> { + IotSceneRuleSaveReqVO updateReqVO = randomPojo(IotSceneRuleSaveReqVO.class, o -> { o.setId(id); o.setStatus(CommonStatusEnum.ENABLE.getStatus()); o.setTriggers(Collections.singletonList(randomPojo(IotSceneRuleDO.Trigger.class))); @@ -87,125 +87,125 @@ public class IotRuleSceneServiceSimpleTest extends BaseMockitoUnitTest { }); // Mock 行为 - IotSceneRuleDO existingRuleScene = randomPojo(IotSceneRuleDO.class, o -> o.setId(id)); - when(ruleSceneMapper.selectById(id)).thenReturn(existingRuleScene); - when(ruleSceneMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1); + IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id)); + when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule); + when(sceneRuleMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1); // 调用 - assertDoesNotThrow(() -> ruleSceneService.updateRuleScene(updateReqVO)); + assertDoesNotThrow(() -> sceneRuleService.updateSceneRule(updateReqVO)); // 验证 - verify(ruleSceneMapper, times(1)).selectById(id); - verify(ruleSceneMapper, times(1)).updateById(any(IotSceneRuleDO.class)); + verify(sceneRuleMapper, times(1)).selectById(id); + verify(sceneRuleMapper, times(1)).updateById(any(IotSceneRuleDO.class)); } @Test - public void testDeleteRuleScene_success() { + public void testDeleteSceneRule_success() { // 准备参数 Long id = randomLongId(); // Mock 行为 - IotSceneRuleDO existingRuleScene = randomPojo(IotSceneRuleDO.class, o -> o.setId(id)); - when(ruleSceneMapper.selectById(id)).thenReturn(existingRuleScene); - when(ruleSceneMapper.deleteById(id)).thenReturn(1); + IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id)); + when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule); + when(sceneRuleMapper.deleteById(id)).thenReturn(1); // 调用 - assertDoesNotThrow(() -> ruleSceneService.deleteRuleScene(id)); + assertDoesNotThrow(() -> sceneRuleService.deleteSceneRule(id)); // 验证 - verify(ruleSceneMapper, times(1)).selectById(id); - verify(ruleSceneMapper, times(1)).deleteById(id); + verify(sceneRuleMapper, times(1)).selectById(id); + verify(sceneRuleMapper, times(1)).deleteById(id); } @Test - public void testGetRuleScene() { + public void testGetSceneRule() { // 准备参数 Long id = randomLongId(); - IotSceneRuleDO expectedRuleScene = randomPojo(IotSceneRuleDO.class, o -> o.setId(id)); + IotSceneRuleDO expectedSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id)); // Mock 行为 - when(ruleSceneMapper.selectById(id)).thenReturn(expectedRuleScene); + when(sceneRuleMapper.selectById(id)).thenReturn(expectedSceneRule); // 调用 - IotSceneRuleDO result = ruleSceneService.getRuleScene(id); + IotSceneRuleDO result = sceneRuleService.getSceneRule(id); // 断言 - assertEquals(expectedRuleScene, result); - verify(ruleSceneMapper, times(1)).selectById(id); + assertEquals(expectedSceneRule, result); + verify(sceneRuleMapper, times(1)).selectById(id); } @Test - public void testUpdateRuleSceneStatus_success() { + public void testUpdateSceneRuleStatus_success() { // 准备参数 Long id = randomLongId(); Integer status = CommonStatusEnum.DISABLE.getStatus(); // Mock 行为 - IotSceneRuleDO existingRuleScene = randomPojo(IotSceneRuleDO.class, o -> { + IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> { o.setId(id); o.setStatus(CommonStatusEnum.ENABLE.getStatus()); }); - when(ruleSceneMapper.selectById(id)).thenReturn(existingRuleScene); - when(ruleSceneMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1); + when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule); + when(sceneRuleMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1); // 调用 - assertDoesNotThrow(() -> ruleSceneService.updateRuleSceneStatus(id, status)); + assertDoesNotThrow(() -> sceneRuleService.updateSceneRuleStatus(id, status)); // 验证 - verify(ruleSceneMapper, times(1)).selectById(id); - verify(ruleSceneMapper, times(1)).updateById(any(IotSceneRuleDO.class)); + verify(sceneRuleMapper, times(1)).selectById(id); + verify(sceneRuleMapper, times(1)).updateById(any(IotSceneRuleDO.class)); } @Test - public void testExecuteRuleSceneByTimer_success() { + public void testExecuteSceneRuleByTimer_success() { // 准备参数 Long id = randomLongId(); // Mock 行为 - IotSceneRuleDO ruleScene = randomPojo(IotSceneRuleDO.class, o -> { + IotSceneRuleDO sceneRule = randomPojo(IotSceneRuleDO.class, o -> { o.setId(id); o.setStatus(CommonStatusEnum.ENABLE.getStatus()); }); - when(ruleSceneMapper.selectById(id)).thenReturn(ruleScene); + when(sceneRuleMapper.selectById(id)).thenReturn(sceneRule); // 调用 - assertDoesNotThrow(() -> ruleSceneService.executeRuleSceneByTimer(id)); + assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id)); // 验证 - verify(ruleSceneMapper, times(1)).selectById(id); + verify(sceneRuleMapper, times(1)).selectById(id); } @Test - public void testExecuteRuleSceneByTimer_notExists() { + public void testExecuteSceneRuleByTimer_notExists() { // 准备参数 Long id = randomLongId(); // Mock 行为 - when(ruleSceneMapper.selectById(id)).thenReturn(null); + when(sceneRuleMapper.selectById(id)).thenReturn(null); // 调用 - 不存在的场景规则应该不会抛异常,只是记录日志 - assertDoesNotThrow(() -> ruleSceneService.executeRuleSceneByTimer(id)); + assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id)); // 验证 - verify(ruleSceneMapper, times(1)).selectById(id); + verify(sceneRuleMapper, times(1)).selectById(id); } @Test - public void testExecuteRuleSceneByTimer_disabled() { + public void testExecuteSceneRuleByTimer_disabled() { // 准备参数 Long id = randomLongId(); // Mock 行为 - IotSceneRuleDO ruleScene = randomPojo(IotSceneRuleDO.class, o -> { + IotSceneRuleDO sceneRule = randomPojo(IotSceneRuleDO.class, o -> { o.setId(id); o.setStatus(CommonStatusEnum.DISABLE.getStatus()); }); - when(ruleSceneMapper.selectById(id)).thenReturn(ruleScene); + when(sceneRuleMapper.selectById(id)).thenReturn(sceneRule); // 调用 - 禁用的场景规则应该不会执行,只是记录日志 - assertDoesNotThrow(() -> ruleSceneService.executeRuleSceneByTimer(id)); + assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id)); // 验证 - verify(ruleSceneMapper, times(1)).selectById(id); + verify(sceneRuleMapper, times(1)).selectById(id); } } From 0d288077d3ae6c0200b0f4de8e8cd093eea446fc Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 15 Aug 2025 14:37:41 +0800 Subject: [PATCH 3/8] =?UTF-8?q?feat:=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E8=8E=B7=E5=8F=96=E8=AE=BE=E5=A4=87=E7=9A=84?= =?UTF-8?q?=E7=B2=BE=E7=AE=80=E4=BF=A1=E6=81=AF=E5=88=97=E8=A1=A8=E6=97=B6?= =?UTF-8?q?=E5=A4=9A=E8=BF=94=E5=9B=9E=E4=B8=80=E4=B8=AA=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=EF=BC=9Astate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/iot/controller/admin/device/IotDeviceController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java index b72717f5f1..3fa6e7a618 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java @@ -132,7 +132,7 @@ public class IotDeviceController { List list = deviceService.getDeviceListByCondition(deviceType, productId); return success(convertList(list, device -> // 只返回 id、name、productId 字段 new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName()) - .setProductId(device.getProductId()))); + .setProductId(device.getProductId()).setState(device.getState()))); } @PostMapping("/import") From 49bf744b740150c722297ed161702c44ccdf4ac3 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 15 Aug 2025 15:02:10 +0800 Subject: [PATCH 4/8] =?UTF-8?q?feat:=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=E5=9C=BA=E6=99=AF=E8=A7=84?= =?UTF-8?q?=E5=88=99=E8=A7=A6=E5=8F=91=E5=99=A8=E5=8C=B9=E9=85=8D=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E6=8E=A5=E5=8F=A3=E5=92=8C=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rule/IotSceneRuleTriggerTypeEnum.java | 14 ++ .../AbstractIotSceneRuleTriggerMatcher.java | 122 +++++++++++ .../DeviceEventPostTriggerMatcher.java | 75 +++++++ .../DevicePropertyPostTriggerMatcher.java | 79 +++++++ .../DeviceServiceInvokeTriggerMatcher.java | 61 ++++++ .../DeviceStateUpdateTriggerMatcher.java | 71 +++++++ .../matcher/IotSceneRuleTriggerMatcher.java | 62 ++++++ .../IotSceneRuleTriggerMatcherManager.java | 150 +++++++++++++ .../scene/matcher/TimerTriggerMatcher.java | 79 +++++++ .../IotSceneRuleTriggerMatcherTest.java | 200 ++++++++++++++++++ 10 files changed, 913 insertions(+) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleTriggerMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java 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 5e502b59d4..b8e37e80c4 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 @@ -63,4 +63,18 @@ public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable { return ARRAYS; } + + /** + * 根据类型值查找触发器类型枚举 + * + * @param typeValue 类型值 + * @return 触发器类型枚举 + */ + public static IotSceneRuleTriggerTypeEnum findTriggerTypeEnum(Integer typeValue) { + return Arrays.stream(IotSceneRuleTriggerTypeEnum.values()) + .filter(type -> type.getType().equals(typeValue)) + .findFirst() + .orElse(null); + } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleTriggerMatcher.java new file mode 100644 index 0000000000..f2775964fd --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleTriggerMatcher.java @@ -0,0 +1,122 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; +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 lombok.extern.slf4j.Slf4j; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; + +/** + * IoT 场景规则触发器匹配器抽象基类 + *

+ * 提供通用的条件评估逻辑和工具方法 + * + * @author HUIHUI + */ +@Slf4j +public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRuleTriggerMatcher { + + /** + * 评估条件是否匹配 + * + * @param sourceValue 源值(来自消息) + * @param operator 操作符 + * @param paramValue 参数值(来自条件配置) + * @return 是否匹配 + */ + protected boolean evaluateCondition(Object sourceValue, String operator, String paramValue) { + try { + // 1. 校验操作符是否合法 + IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); + if (operatorEnum == null) { + log.warn("[evaluateCondition][存在错误的操作符({})]", operator); + return false; + } + + // 2. 构建 Spring 表达式变量 + Map springExpressionVariables = new HashMap<>(); + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue); + + // 处理参数值 + if (StrUtil.isNotBlank(paramValue)) { + // 处理多值情况(如 IN、BETWEEN 操作符) + if (paramValue.contains(",")) { + List paramValues = StrUtil.split(paramValue, ','); + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, + convertList(paramValues, NumberUtil::parseDouble)); + } else { + // 处理单值情况 + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, + NumberUtil.parseDouble(paramValue)); + } + } + + // 3. 计算 Spring 表达式 + return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables); + } catch (Exception e) { + log.error("[evaluateCondition][条件评估异常] sourceValue: {}, operator: {}, paramValue: {}", + sourceValue, operator, paramValue, e); + return false; + } + } + + /** + * 检查基础触发器参数是否有效 + * + * @param trigger 触发器配置 + * @return 是否有效 + */ + protected boolean isBasicTriggerValid(IotSceneRuleDO.Trigger trigger) { + return trigger != null && trigger.getType() != null; + } + + /** + * 检查操作符和值是否有效 + * + * @param trigger 触发器配置 + * @return 是否有效 + */ + protected boolean isOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { + return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue()); + } + + /** + * 检查标识符是否匹配 + * + * @param expectedIdentifier 期望的标识符 + * @param actualIdentifier 实际的标识符 + * @return 是否匹配 + */ + protected boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) { + return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier); + } + + /** + * 记录匹配成功日志 + * + * @param message 设备消息 + * @param trigger 触发器配置 + */ + protected void logMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType()); + } + + /** + * 记录匹配失败日志 + * + * @param message 设备消息 + * @param trigger 触发器配置 + * @param reason 失败原因 + */ + protected void logMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { + log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason); + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java new file mode 100644 index 0000000000..dd30b068d4 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +import cn.hutool.core.util.StrUtil; +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 org.springframework.stereotype.Component; + +/** + * 设备事件上报触发器匹配器 + *

+ * 处理设备事件上报的触发器匹配逻辑 + * + * @author HUIHUI + */ +@Component +public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { + + /** + * 设备事件上报消息方法 + */ + private static final String DEVICE_EVENT_POST_METHOD = "thing.event.post"; + + @Override + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { + return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + // 1. 基础参数校验 + if (!isBasicTriggerValid(trigger)) { + logMatchFailure(message, trigger, "触发器基础参数无效"); + return false; + } + + // 2. 检查消息方法是否匹配 + if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) { + logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod()); + return false; + } + + // 3. 检查标识符是否匹配 + String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); + if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { + logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); + return false; + } + + // 4. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配 + // 但如果配置了操作符和值,则需要进行条件匹配 + if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) { + Object eventData = message.getData(); + if (eventData == null) { + logMatchFailure(message, trigger, "消息中事件数据为空"); + return false; + } + + boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); + if (!matched) { + logMatchFailure(message, trigger, "事件数据条件不匹配"); + return false; + } + } + + logMatchSuccess(message, trigger); + return true; + } + + @Override + public int getPriority() { + return 30; // 中等优先级 + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java new file mode 100644 index 0000000000..b508612b6f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java @@ -0,0 +1,79 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +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 org.springframework.stereotype.Component; + +/** + * 设备属性上报触发器匹配器 + *

+ * 处理设备属性数据上报的触发器匹配逻辑 + * + * @author HUIHUI + */ +@Component +public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { + + /** + * 设备属性上报消息方法 + */ + private static final String DEVICE_PROPERTY_POST_METHOD = "thing.property.post"; + + @Override + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { + return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + // 1. 基础参数校验 + if (!isBasicTriggerValid(trigger)) { + logMatchFailure(message, trigger, "触发器基础参数无效"); + return false; + } + + // 2. 检查消息方法是否匹配 + if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) { + logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod()); + return false; + } + + // 3. 检查标识符是否匹配 + String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); + if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { + logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); + return false; + } + + // 4. 检查操作符和值是否有效 + if (!isOperatorAndValueValid(trigger)) { + logMatchFailure(message, trigger, "操作符或值无效"); + return false; + } + + // 5. 获取属性值 + Object propertyValue = message.getData(); + if (propertyValue == null) { + logMatchFailure(message, trigger, "消息中属性值为空"); + return false; + } + + // 6. 使用条件评估器进行匹配 + boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); + + if (matched) { + logMatchSuccess(message, trigger); + } else { + logMatchFailure(message, trigger, "属性值条件不匹配"); + } + + return matched; + } + + @Override + public int getPriority() { + return 20; // 中等优先级 + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java new file mode 100644 index 0000000000..0f77e0b4e6 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +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 org.springframework.stereotype.Component; + +/** + * 设备服务调用触发器匹配器 + *

+ * 处理设备服务调用的触发器匹配逻辑 + * + * @author HUIHUI + */ +@Component +public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { + + /** + * 设备服务调用消息方法 + */ + private static final String DEVICE_SERVICE_INVOKE_METHOD = "thing.service.invoke"; + + @Override + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { + return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + // 1. 基础参数校验 + if (!isBasicTriggerValid(trigger)) { + logMatchFailure(message, trigger, "触发器基础参数无效"); + return false; + } + + // 2. 检查消息方法是否匹配 + if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) { + logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod()); + return false; + } + + // 3. 检查标识符是否匹配 + String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); + if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { + logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); + return false; + } + + // 4. 对于服务调用触发器,通常只需要匹配服务标识符即可 + // 不需要检查操作符和值,因为服务调用本身就是触发条件 + + logMatchSuccess(message, trigger); + return true; + } + + @Override + public int getPriority() { + return 40; // 较低优先级 + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java new file mode 100644 index 0000000000..2e73081242 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java @@ -0,0 +1,71 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +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 org.springframework.stereotype.Component; + +/** + * 设备状态更新触发器匹配器 + *

+ * 处理设备上下线状态变更的触发器匹配逻辑 + * + * @author HUIHUI + */ +@Component +public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { + + /** + * 设备状态更新消息方法 + */ + private static final String DEVICE_STATE_UPDATE_METHOD = "thing.state.update"; + + @Override + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { + return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + // 1. 基础参数校验 + if (!isBasicTriggerValid(trigger)) { + logMatchFailure(message, trigger, "触发器基础参数无效"); + return false; + } + + // 2. 检查消息方法是否匹配 + if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) { + logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod()); + return false; + } + + // 3. 检查操作符和值是否有效 + if (!isOperatorAndValueValid(trigger)) { + logMatchFailure(message, trigger, "操作符或值无效"); + return false; + } + + // 4. 获取设备状态值 + Object stateValue = message.getData(); + if (stateValue == null) { + logMatchFailure(message, trigger, "消息中设备状态值为空"); + return false; + } + + // 5. 使用条件评估器进行匹配 + boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); + + if (matched) { + logMatchSuccess(message, trigger); + } else { + logMatchFailure(message, trigger, "状态值条件不匹配"); + } + + return matched; + } + + @Override + public int getPriority() { + return 10; // 高优先级 + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java new file mode 100644 index 0000000000..56f7772817 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java @@ -0,0 +1,62 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +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; + +/** + * IoT 场景规则触发器匹配策略接口 + *

+ * 用于实现不同类型触发器的匹配逻辑,遵循策略模式设计 + * + * @author HUIHUI + */ +public interface IotSceneRuleTriggerMatcher { + + /** + * 获取支持的触发器类型 + * + * @return 触发器类型枚举 + */ + IotSceneRuleTriggerTypeEnum getSupportedTriggerType(); + + /** + * 检查触发器是否匹配消息 + * + * @param message 设备消息 + * @param trigger 触发器配置 + * @return 是否匹配 + */ + boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger); + + /** + * 获取匹配优先级(数值越小优先级越高) + *

+ * 用于在多个匹配器支持同一触发器类型时确定优先级 + * + * @return 优先级数值 + */ + default int getPriority() { + return 100; + } + + /** + * 获取匹配器名称,用于日志和调试 + * + * @return 匹配器名称 + */ + default String getMatcherName() { + return this.getClass().getSimpleName(); + } + + /** + * 是否启用该匹配器 + *

+ * 可用于动态开关某些匹配器 + * + * @return 是否启用 + */ + default boolean isEnabled() { + return true; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java new file mode 100644 index 0000000000..2c300b1871 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java @@ -0,0 +1,150 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +import cn.hutool.core.collection.CollUtil; +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 lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum.findTriggerTypeEnum; + +/** + * IoT 场景规则触发器匹配管理器 + *

+ * 负责管理所有触发器匹配器,并提供统一的匹配入口 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class IotSceneRuleTriggerMatcherManager { + + /** + * 触发器匹配器映射表 + * Key: 触发器类型枚举 + * Value: 对应的匹配器实例 + */ + private final Map matcherMap; + + /** + * 所有匹配器列表(按优先级排序) + */ + private final List allMatchers; + + public IotSceneRuleTriggerMatcherManager(List matchers) { + if (CollUtil.isEmpty(matchers)) { + log.warn("[IotSceneRuleTriggerMatcherManager][没有找到任何触发器匹配器]"); + this.matcherMap = new HashMap<>(); + this.allMatchers = new ArrayList<>(); + return; + } + + // 按优先级排序并过滤启用的匹配器 + this.allMatchers = matchers.stream() + .filter(IotSceneRuleTriggerMatcher::isEnabled) + .sorted(Comparator.comparing(IotSceneRuleTriggerMatcher::getPriority)) + .collect(Collectors.toList()); + + // 构建匹配器映射表 + this.matcherMap = this.allMatchers.stream() + .collect(Collectors.toMap( + IotSceneRuleTriggerMatcher::getSupportedTriggerType, + Function.identity(), + (existing, replacement) -> { + log.warn("[IotSceneRuleTriggerMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]", + existing.getSupportedTriggerType(), + existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName()); + return existing.getPriority() <= replacement.getPriority() ? existing : replacement; + }, + LinkedHashMap::new + )); + + log.info("[IotSceneRuleTriggerMatcherManager][初始化完成,共加载 {} 个触发器匹配器]", this.matcherMap.size()); + this.matcherMap.forEach((type, matcher) -> + log.info("[IotSceneRuleTriggerMatcherManager][触发器类型: {}, 匹配器: {}, 优先级: {}]", + type, matcher.getMatcherName(), matcher.getPriority())); + } + + /** + * 检查触发器是否匹配消息 + * + * @param message 设备消息 + * @param trigger 触发器配置 + * @return 是否匹配 + */ + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + if (message == null || trigger == null || trigger.getType() == null) { + log.debug("[isMatched][参数无效] message: {}, trigger: {}", message, trigger); + return false; + } + + // 根据触发器类型查找对应的匹配器 + IotSceneRuleTriggerTypeEnum triggerType = findTriggerTypeEnum(trigger.getType()); + if (triggerType == null) { + log.warn("[isMatched][未知的触发器类型: {}]", trigger.getType()); + return false; + } + + IotSceneRuleTriggerMatcher matcher = matcherMap.get(triggerType); + if (matcher == null) { + log.warn("[isMatched][触发器类型({})没有对应的匹配器]", triggerType); + return false; + } + + try { + return matcher.isMatched(message, trigger); + } catch (Exception e) { + log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}, matcher: {}", + message, trigger, matcher.getMatcherName(), e); + return false; + } + } + + /** + * 获取所有支持的触发器类型 + * + * @return 支持的触发器类型列表 + */ + public Set getSupportedTriggerTypes() { + return new HashSet<>(matcherMap.keySet()); + } + + /** + * 获取指定触发器类型的匹配器 + * + * @param triggerType 触发器类型 + * @return 匹配器实例,如果不存在则返回 null + */ + public IotSceneRuleTriggerMatcher getMatcher(IotSceneRuleTriggerTypeEnum triggerType) { + return matcherMap.get(triggerType); + } + + /** + * 获取所有匹配器的统计信息 + * + * @return 统计信息映射表 + */ + public Map getMatcherStatistics() { + Map statistics = new HashMap<>(); + statistics.put("totalMatchers", allMatchers.size()); + statistics.put("enabledMatchers", matcherMap.size()); + statistics.put("supportedTriggerTypes", getSupportedTriggerTypes()); + + Map matcherDetails = new HashMap<>(); + matcherMap.forEach((type, matcher) -> { + Map detail = new HashMap<>(); + detail.put("matcherName", matcher.getMatcherName()); + detail.put("priority", matcher.getPriority()); + detail.put("enabled", matcher.isEnabled()); + matcherDetails.put(type.name(), detail); + }); + statistics.put("matcherDetails", matcherDetails); + + return statistics; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java new file mode 100644 index 0000000000..a0605ca65f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java @@ -0,0 +1,79 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +import cn.hutool.core.util.StrUtil; +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 org.springframework.stereotype.Component; + +/** + * 定时触发器匹配器 + *

+ * 处理定时触发的触发器匹配逻辑 + * 注意:定时触发器不依赖设备消息,主要用于定时任务场景 + * + * @author HUIHUI + */ +@Component +public class TimerTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { + + @Override + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { + return IotSceneRuleTriggerTypeEnum.TIMER; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + // 1. 基础参数校验 + if (!isBasicTriggerValid(trigger)) { + logMatchFailure(message, trigger, "触发器基础参数无效"); + return false; + } + + // 2. 检查 CRON 表达式是否存在 + if (StrUtil.isBlank(trigger.getCronExpression())) { + logMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); + return false; + } + + // 3. 定时触发器通常不依赖具体的设备消息 + // 它是通过定时任务调度器触发的,这里主要是验证配置的有效性 + + // 4. 可以添加 CRON 表达式格式验证 + if (!isValidCronExpression(trigger.getCronExpression())) { + logMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); + return false; + } + + logMatchSuccess(message, trigger); + return true; + } + + /** + * 验证 CRON 表达式格式是否有效 + * + * @param cronExpression CRON 表达式 + * @return 是否有效 + */ + private boolean isValidCronExpression(String cronExpression) { + try { + // 简单的 CRON 表达式格式验证 + // 标准 CRON 表达式应该有 6 或 7 个字段(秒 分 时 日 月 周 [年]) + String[] fields = cronExpression.trim().split("\\s+"); + return fields.length >= 6 && fields.length <= 7; + } catch (Exception e) { + return false; + } + } + + @Override + public int getPriority() { + return 50; // 最低优先级,因为定时触发器不依赖消息 + } + + @Override + public boolean isEnabled() { + // 定时触发器可以根据配置动态启用/禁用 + return true; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java new file mode 100644 index 0000000000..9903e8cc2f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java @@ -0,0 +1,200 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * IoT 场景规则触发器匹配器测试类 + * + * @author HUIHUI + */ +public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { + + private IotSceneRuleTriggerMatcherManager matcherManager; + private List matchers; + + @BeforeEach + void setUp() { + // 创建所有匹配器实例 + matchers = Arrays.asList( + new DeviceStateUpdateTriggerMatcher(), + new DevicePropertyPostTriggerMatcher(), + new DeviceEventPostTriggerMatcher(), + new DeviceServiceInvokeTriggerMatcher(), + new TimerTriggerMatcher() + ); + + // 初始化匹配器管理器 + matcherManager = new IotSceneRuleTriggerMatcherManager(matchers); + } + + @Test + void testDeviceStateUpdateTriggerMatcher() { + // 1. 准备测试数据 + IotDeviceMessage message = IotDeviceMessage.builder() + .requestId("test-001") + .method("thing.state.update") + .data(1) // 在线状态 + .build(); + + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); + trigger.setOperator("="); + trigger.setValue("1"); + + // 2. 执行测试 + boolean matched = matcherManager.isMatched(message, trigger); + + // 3. 验证结果 + assertTrue(matched, "设备状态更新触发器应该匹配"); + } + + @Test + void testDevicePropertyPostTriggerMatcher() { + // 1. 准备测试数据 + HashMap params = new HashMap<>(); + IotDeviceMessage message = IotDeviceMessage.builder() + .requestId("test-002") + .method("thing.property.post") + .data(25.5) // 温度值 + .params(params) + .build(); + // 模拟标识符 + params.put("identifier", "temperature"); + + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); + trigger.setIdentifier("temperature"); + trigger.setOperator(">"); + trigger.setValue("20"); + + // 2. 执行测试 + boolean matched = matcherManager.isMatched(message, trigger); + + // 3. 验证结果 + assertTrue(matched, "设备属性上报触发器应该匹配"); + } + + @Test + void testDeviceEventPostTriggerMatcher() { + // 1. 准备测试数据 + HashMap params = new HashMap<>(); + IotDeviceMessage message = IotDeviceMessage.builder() + .requestId("test-003") + .method("thing.event.post") + .data("alarm_data") + .params(params) + .build(); + // 模拟标识符 + params.put("identifier", "high_temperature_alarm"); + + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType()); + trigger.setIdentifier("high_temperature_alarm"); + + // 2. 执行测试 + boolean matched = matcherManager.isMatched(message, trigger); + + // 3. 验证结果 + assertTrue(matched, "设备事件上报触发器应该匹配"); + } + + @Test + void testDeviceServiceInvokeTriggerMatcher() { + // 1. 准备测试数据 + HashMap params = new HashMap<>(); + IotDeviceMessage message = IotDeviceMessage.builder() + .requestId("test-004") + .method("thing.service.invoke") + .msg("alarm_data") + .params(params) + .build(); + // 模拟标识符 + params.put("identifier", "restart_device"); + + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType()); + trigger.setIdentifier("restart_device"); + + // 2. 执行测试 + boolean matched = matcherManager.isMatched(message, trigger); + + // 3. 验证结果 + assertTrue(matched, "设备服务调用触发器应该匹配"); + } + + @Test + void testTimerTriggerMatcher() { + // 1. 准备测试数据 + IotDeviceMessage message = IotDeviceMessage.builder() + .requestId("test-005") + .method("timer.trigger") // 定时触发器不依赖具体消息方法 + .build(); + + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); + trigger.setCronExpression("0 0 12 * * ?"); // 每天中午12点 + + // 2. 执行测试 + boolean matched = matcherManager.isMatched(message, trigger); + + // 3. 验证结果 + assertTrue(matched, "定时触发器应该匹配"); + } + + @Test + void testInvalidTriggerType() { + // 1. 准备测试数据 + IotDeviceMessage message = IotDeviceMessage.builder() + .requestId("test-006") + .method("unknown.method") + .build(); + + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(999); // 无效的触发器类型 + + // 2. 执行测试 + boolean matched = matcherManager.isMatched(message, trigger); + + // 3. 验证结果 + assertFalse(matched, "无效的触发器类型应该不匹配"); + } + + @Test + void testMatcherManagerStatistics() { + // 1. 执行测试 + var statistics = matcherManager.getMatcherStatistics(); + + // 2. 验证结果 + assertNotNull(statistics); + assertEquals(5, statistics.get("totalMatchers")); + assertEquals(5, statistics.get("enabledMatchers")); + assertNotNull(statistics.get("supportedTriggerTypes")); + assertNotNull(statistics.get("matcherDetails")); + } + + @Test + void testGetSupportedTriggerTypes() { + // 1. 执行测试 + var supportedTypes = matcherManager.getSupportedTriggerTypes(); + + // 2. 验证结果 + assertNotNull(supportedTypes); + assertEquals(5, supportedTypes.size()); + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE)); + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST)); + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST)); + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE)); + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.TIMER)); + } +} From cfb5230c2a218f8f23551a1a404f0aeb7ce6b56b Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 15 Aug 2025 16:22:29 +0800 Subject: [PATCH 5/8] =?UTF-8?q?perf:=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=A7=84=E5=88=99=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rule/IotSceneRuleTriggerTypeEnum.java | 3 - .../rule/scene/IotSceneRuleService.java | 14 +- .../rule/scene/IotSceneRuleServiceImpl.java | 202 ++++++++++-------- .../AbstractIotSceneRuleTriggerMatcher.java | 1 + .../DeviceEventPostTriggerMatcher.java | 4 +- .../DevicePropertyPostTriggerMatcher.java | 4 +- .../DeviceServiceInvokeTriggerMatcher.java | 4 +- .../DeviceStateUpdateTriggerMatcher.java | 4 +- .../matcher/IotSceneRuleTriggerMatcher.java | 1 + .../IotSceneRuleTriggerMatcherManager.java | 1 + .../scene/matcher/TimerTriggerMatcher.java | 1 + 11 files changed, 132 insertions(+), 107 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 b8e37e80c4..16b5e79446 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 @@ -18,9 +18,6 @@ import java.util.Arrays; @Getter public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable { - @Deprecated - DEVICE(1), // 设备触发 // TODO @puhui999:@芋艿:这个可以作废 - // TODO @芋艿:后续“对应”部分,要 @下,等包结构梳理完; /** * 设备上下线变更 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleService.java index 2916848b0a..bdbc4f39b3 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleService.java @@ -83,15 +83,19 @@ public interface IotSceneRuleService { /** * 【缓存】获得指定设备的场景列表 * - * @param productKey 产品 Key - * @param deviceName 设备名称 + * @param productId 产品 ID + * @param deviceId 设备 ID * @return 场景列表 */ - List getSceneRuleListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName); + List getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId); /** - * 基于 {@link IotSceneRuleTriggerTypeEnum#DEVICE} 场景,执行规则场景 - * + * 基于 {@link IotSceneRuleTriggerTypeEnum} 场景,执行规则场景 + * 1. {@link IotSceneRuleTriggerTypeEnum#DEVICE_STATE_UPDATE} + * 2. {@link IotSceneRuleTriggerTypeEnum#DEVICE_PROPERTY_POST} + * {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST} + * 3. {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST} + * {@link IotSceneRuleTriggerTypeEnum#DEVICE_SERVICE_INVOKE} * @param message 消息 */ void executeSceneRuleByDevice(IotDeviceMessage message); 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 39efa79e48..295a796d4c 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 @@ -7,6 +7,7 @@ import cn.hutool.core.text.CharPool; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; @@ -30,6 +31,7 @@ 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.IotSceneRuleTriggerMatcherManager; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -58,17 +60,16 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @Resource private IotSceneRuleMapper sceneRuleMapper; - @Resource - private List sceneRuleActions; - @Resource(name = "iotSchedulerManager") private IotSchedulerManager schedulerManager; - @Resource private IotProductService productService; - @Resource private IotDeviceService deviceService; + @Resource + private IotSceneRuleTriggerMatcherManager triggerMatcherManager; + @Resource + private List sceneRuleActions; @Override public Long createSceneRule(IotSceneRuleSaveReqVO createReqVO) { @@ -139,14 +140,11 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { // TODO 芋艿,缓存待实现 @Override @TenantIgnore // 忽略租户隔离:因为 IotSceneRuleMessageHandler 调用时,一般未传递租户,所以需要忽略 - public List getSceneRuleListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) { - // TODO @puhui999:一些注释,看看要不要优化下; - // 注意:旧的测试代码已删除,因为使用了废弃的数据结构 - // 如需测试,请使用上面的新结构测试代码示例 + public List getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId) { List list = sceneRuleMapper.selectList(); // 只返回启用状态的规则场景 List enabledList = filterList(list, - sceneRule -> CommonStatusEnum.ENABLE.getStatus().equals(sceneRule.getStatus())); + sceneRule -> CommonStatusEnum.isEnable(sceneRule.getStatus())); // 根据 productKey 和 deviceName 进行匹配 return filterList(enabledList, sceneRule -> { @@ -156,59 +154,29 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { for (IotSceneRuleDO.Trigger trigger : sceneRule.getTriggers()) { // 检查触发器是否匹配指定的产品和设备 - if (isMatchProductAndDevice(trigger, productKey, deviceName)) { - return true; + try { + // 1. 检查产品是否匹配 + if (trigger.getProductId() == null) { + return false; + } + if (trigger.getDeviceId() == null) { + return false; + } + // 检查是否是全部设备的特殊标识 + if (IotDeviceDO.DEVICE_ID_ALL.equals(trigger.getDeviceId())) { + return true; // 匹配所有设备 + } + // 检查具体设备 ID 是否匹配 + return ObjUtil.equal(productId, trigger.getProductId()) && ObjUtil.equal(deviceId, trigger.getDeviceId()); + } catch (Exception e) { + log.warn("[isMatchProductAndDevice][产品({}) 设备({}) 匹配触发器异常]", productId, deviceId, e); + return false; } } return false; }); } - /** - * 检查触发器是否匹配指定的产品和设备 - * - * @param trigger 触发器 - * @param productKey 产品标识 - * @param deviceName 设备名称 - * @return 是否匹配 - */ - private boolean isMatchProductAndDevice(IotSceneRuleDO.Trigger trigger, String productKey, String deviceName) { - try { - // 1. 检查产品是否匹配 - if (trigger.getProductId() != null) { - // 通过 productKey 获取产品信息 - IotProductDO product = productService.getProductByProductKey(productKey); - if (product == null || !trigger.getProductId().equals(product.getId())) { - return false; - } - } - - // 2. 检查设备是否匹配 - if (trigger.getDeviceId() != null) { - // 通过 productKey 和 deviceName 获取设备信息 - IotDeviceDO device = deviceService.getDeviceFromCache(productKey, deviceName); - if (device == null) { - return false; - } - - // 检查是否是全部设备的特殊标识 - if (IotDeviceDO.DEVICE_ID_ALL.equals(trigger.getDeviceId())) { - return true; // 匹配所有设备 - } - - // 检查具体设备ID是否匹配 - if (!trigger.getDeviceId().equals(device.getId())) { - return false; - } - } - - return true; - } catch (Exception e) { - log.warn("[isMatchProductAndDevice][产品({}) 设备({}) 匹配触发器异常]", productKey, deviceName, e); - return false; - } - } - @Override public void executeSceneRuleByDevice(IotDeviceMessage message) { // TODO @芋艿:这里的 tenantId,通过设备获取; @@ -273,55 +241,95 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { } // 1.3 获取匹配的规则场景 - List sceneRules = getSceneRuleListByProductKeyAndDeviceNameFromCache( - product.getProductKey(), device.getDeviceName()); + List sceneRules = getSceneRuleListByProductIdAndDeviceIdFromCache( + product.getId(), device.getId()); if (CollUtil.isEmpty(sceneRules)) { return sceneRules; } - // 2. 匹配 trigger 触发器的条件 - return filterList(sceneRules, sceneRule -> { - for (IotSceneRuleDO.Trigger trigger : sceneRule.getTriggers()) { - // 2.1 检查触发器类型,根据新的枚举值进行匹配 - // TODO @芋艿:需要根据新的触发器类型枚举进行适配 - // 原来使用 IotSceneRuleTriggerTypeEnum.DEVICE,新结构可能有不同的类型 + // 2. 使用重构后的触发器匹配逻辑 + return filterList(sceneRules, sceneRule -> matchSceneRuleTriggers(message, sceneRule)); + } - // 2.2 条件分组为空,说明没有匹配的条件,因此不匹配 - if (CollUtil.isEmpty(trigger.getConditionGroups())) { - return false; - } + /** + * 匹配场景规则的所有触发器 + * + * @param message 设备消息 + * @param sceneRule 场景规则 + * @return 是否匹配 + */ + private boolean matchSceneRuleTriggers(IotDeviceMessage message, IotSceneRuleDO sceneRule) { + if (CollUtil.isEmpty(sceneRule.getTriggers())) { + log.debug("[matchSceneRuleTriggers][规则场景({}) 没有配置触发器]", sceneRule.getId()); + return false; + } - // 2.3 检查条件分组:分组与分组之间是"或"的关系,条件与条件之间是"且"的关系 - boolean anyGroupMatched = false; - for (List conditionGroup : trigger.getConditionGroups()) { - if (CollUtil.isEmpty(conditionGroup)) { - continue; - } + for (IotSceneRuleDO.Trigger trigger : sceneRule.getTriggers()) { + if (matchSingleTrigger(message, trigger, sceneRule)) { + log.info("[matchSceneRuleTriggers][消息({}) 匹配到规则场景编号({}) 的触发器({})]", + message.getRequestId(), sceneRule.getId(), trigger.getType()); + return true; + } + } + return false; + } - // 检查当前分组中的所有条件是否都匹配(且关系) - boolean allConditionsMatched = true; - for (IotSceneRuleDO.TriggerCondition condition : conditionGroup) { - // TODO @芋艿:这里需要实现具体的条件匹配逻辑 - // 根据新的 TriggerCondition 结构进行匹配 - if (!isTriggerConditionMatched(message, condition, sceneRule, trigger)) { - allConditionsMatched = false; - break; - } - } + /** + * 匹配单个触发器 + * + * @param message 设备消息 + * @param trigger 触发器 + * @param sceneRule 场景规则(用于日志) + * @return 是否匹配 + */ + private boolean matchSingleTrigger(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) { + try { + // 2. 检查触发器的条件分组 + return triggerMatcherManager.isMatched(message, trigger) && isTriggerConditionGroupsMatched(message, trigger, sceneRule); + } catch (Exception e) { + log.error("[matchSingleTrigger][触发器匹配异常] sceneRuleId: {}, triggerType: {}, message: {}", + sceneRule.getId(), trigger.getType(), message, e); + return false; + } + } - if (allConditionsMatched) { - anyGroupMatched = true; - break; // 有一个分组匹配即可 - } - } + /** + * 检查触发器的条件分组是否匹配 + * + * @param message 设备消息 + * @param trigger 触发器 + * @param sceneRule 场景规则(用于日志) + * @return 是否匹配 + */ + private boolean isTriggerConditionGroupsMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) { + // 如果没有条件分组,则认为匹配成功(只依赖基础触发器匹配) + if (CollUtil.isEmpty(trigger.getConditionGroups())) { + return true; + } - if (anyGroupMatched) { - log.info("[getMatchedSceneRuleList][消息({}) 匹配到规则场景编号({}) 的触发器({})]", message, sceneRule.getId(), trigger); - return true; + // 检查条件分组:分组与分组之间是"或"的关系,条件与条件之间是"且"的关系 + for (List conditionGroup : trigger.getConditionGroups()) { + if (CollUtil.isEmpty(conditionGroup)) { + continue; + } + + // 检查当前分组中的所有条件是否都匹配(且关系) + boolean allConditionsMatched = true; + for (IotSceneRuleDO.TriggerCondition condition : conditionGroup) { + if (!isTriggerConditionMatched(message, condition, sceneRule, trigger)) { + allConditionsMatched = false; + break; } } - return false; - }); + + // 如果当前分组的所有条件都匹配,则整个触发器匹配成功 + if (allConditionsMatched) { + return true; + } + } + + // 所有分组都不匹配 + return false; } /** @@ -549,4 +557,8 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { }); } + private IotSceneRuleServiceImpl getSelf() { + return SpringUtil.getBean(IotSceneRuleServiceImpl.class); + } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleTriggerMatcher.java index f2775964fd..2314bbc4f6 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleTriggerMatcher.java @@ -119,4 +119,5 @@ public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRule protected void logMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason); } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java index dd30b068d4..1ee0cdda81 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; 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.core.util.IotDeviceMessageUtils; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; @@ -20,7 +21,7 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa /** * 设备事件上报消息方法 */ - private static final String DEVICE_EVENT_POST_METHOD = "thing.event.post"; + private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod(); @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { @@ -72,4 +73,5 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa public int getPriority() { return 30; // 中等优先级 } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java index b508612b6f..7ed00519ec 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; +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; @@ -19,7 +20,7 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge /** * 设备属性上报消息方法 */ - private static final String DEVICE_PROPERTY_POST_METHOD = "thing.property.post"; + private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(); @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { @@ -76,4 +77,5 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge public int getPriority() { return 20; // 中等优先级 } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java index 0f77e0b4e6..996fe173f9 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; +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; @@ -19,7 +20,7 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTrigg /** * 设备服务调用消息方法 */ - private static final String DEVICE_SERVICE_INVOKE_METHOD = "thing.service.invoke"; + private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(); @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { @@ -58,4 +59,5 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTrigg public int getPriority() { return 40; // 较低优先级 } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java index 2e73081242..aec372c51b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; +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; @@ -18,7 +19,7 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger /** * 设备状态更新消息方法 */ - private static final String DEVICE_STATE_UPDATE_METHOD = "thing.state.update"; + private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(); @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { @@ -68,4 +69,5 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger public int getPriority() { return 10; // 高优先级 } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java index 56f7772817..bf111a2663 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java @@ -59,4 +59,5 @@ public interface IotSceneRuleTriggerMatcher { default boolean isEnabled() { return true; } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java index 2c300b1871..6e6c383a95 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java @@ -147,4 +147,5 @@ public class IotSceneRuleTriggerMatcherManager { return statistics; } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java index a0605ca65f..c37a10a13f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java @@ -76,4 +76,5 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { // 定时触发器可以根据配置动态启用/禁用 return true; } + } From 93aaffddfefad7ecc2039a2da448dcbe8cb7be96 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 15 Aug 2025 17:27:15 +0800 Subject: [PATCH 6/8] =?UTF-8?q?feat:=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=E5=9C=BA=E6=99=AF=E8=A7=84?= =?UTF-8?q?=E5=88=99=E8=A7=A6=E5=8F=91=E5=99=A8=E5=8C=B9=E9=85=8D=E5=AD=90?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E7=AD=96=E7=95=A5=E6=8E=A5=E5=8F=A3=E5=92=8C?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rule/IotSceneRuleConditionLevelEnum.java | 74 +++++ .../rule/scene/IotSceneRuleServiceImpl.java | 126 +------- ....java => AbstractIotSceneRuleMatcher.java} | 99 ++++-- .../matcher/CurrentTimeConditionMatcher.java | 177 +++++++++++ .../DeviceEventPostTriggerMatcher.java | 19 +- .../DevicePropertyConditionMatcher.java | 80 +++++ .../DevicePropertyPostTriggerMatcher.java | 23 +- .../DeviceServiceInvokeTriggerMatcher.java | 15 +- .../matcher/DeviceStateConditionMatcher.java | 73 +++++ .../DeviceStateUpdateTriggerMatcher.java | 21 +- .../scene/matcher/IotSceneRuleMatcher.java | 123 ++++++++ .../matcher/IotSceneRuleMatcherManager.java | 296 ++++++++++++++++++ .../matcher/IotSceneRuleTriggerMatcher.java | 63 ---- .../IotSceneRuleTriggerMatcherManager.java | 151 --------- .../scene/matcher/TimerTriggerMatcher.java | 15 +- .../IotSceneRuleTriggerMatcherTest.java | 8 +- 16 files changed, 965 insertions(+), 398 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionLevelEnum.java rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/{AbstractIotSceneRuleTriggerMatcher.java => AbstractIotSceneRuleMatcher.java} (64%) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionLevelEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionLevelEnum.java new file mode 100644 index 0000000000..c83b72c1f5 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionLevelEnum.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.iot.enums.rule; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * IoT 场景规则条件层级枚举 + *

+ * 用于区分主条件(触发器级别)和子条件(条件分组级别) + * + * @author HUIHUI + */ +@AllArgsConstructor +@Getter +public enum IotSceneRuleConditionLevelEnum { + + /** + * 主条件 - 触发器级别的条件 + * 用于判断触发器本身是否匹配(如消息类型、设备标识等) + */ + PRIMARY(1, "主条件"), + + /** + * 子条件 - 条件分组级别的条件 + * 用于判断具体的业务条件(如设备状态、属性值、时间条件等) + */ + SECONDARY(2, "子条件"); + + /** + * 条件层级 + */ + private final Integer level; + + /** + * 条件层级名称 + */ + private final String name; + + /** + * 根据层级值获取枚举 + * + * @param level 层级值 + * @return 条件层级枚举 + */ + public static IotSceneRuleConditionLevelEnum levelOf(Integer level) { + if (level == null) { + return null; + } + for (IotSceneRuleConditionLevelEnum levelEnum : values()) { + if (levelEnum.getLevel().equals(level)) { + return levelEnum; + } + } + return null; + } + + /** + * 判断是否为主条件 + * + * @return 是否为主条件 + */ + public boolean isPrimary() { + return this == PRIMARY; + } + + /** + * 判断是否为子条件 + * + * @return 是否为子条件 + */ + public boolean isSecondary() { + return this == SECONDARY; + } +} 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 295a796d4c..fc3e96798f 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 @@ -19,19 +19,17 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO; import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO; 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.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.enums.rule.IotSceneRuleConditionOperatorEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; 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.IotSceneRuleTriggerMatcherManager; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -67,7 +65,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { @Resource private IotDeviceService deviceService; @Resource - private IotSceneRuleTriggerMatcherManager triggerMatcherManager; + private IotSceneRuleMatcherManager matcherManager; @Resource private List sceneRuleActions; @@ -285,7 +283,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { private boolean matchSingleTrigger(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) { try { // 2. 检查触发器的条件分组 - return triggerMatcherManager.isMatched(message, trigger) && isTriggerConditionGroupsMatched(message, trigger, sceneRule); + return matcherManager.isMatched(message, trigger) && isTriggerConditionGroupsMatched(message, trigger, sceneRule); } catch (Exception e) { log.error("[matchSingleTrigger][触发器匹配异常] sceneRuleId: {}, triggerType: {}, message: {}", sceneRule.getId(), trigger.getType(), message, e); @@ -333,7 +331,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { } /** - * 基于消息,判断触发器的条件是否匹配 + * 基于消息,判断触发器的子条件是否匹配 * * @param message 设备消息 * @param condition 触发条件 @@ -344,21 +342,8 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { private boolean isTriggerConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) { try { - // 1. 根据条件类型进行匹配 - if (IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType().equals(condition.getType())) { - // 设备状态条件匹配 - return matchDeviceStateCondition(message, condition); - } else if (IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType().equals(condition.getType())) { - // 设备属性条件匹配 - return matchDevicePropertyCondition(message, condition); - } else if (IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType().equals(condition.getType())) { - // 当前时间条件匹配 - return matchCurrentTimeCondition(condition); - } else { - log.warn("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 存在未知的条件类型({})]", - sceneRule.getId(), trigger, condition.getType()); - return false; - } + // 使用重构后的条件匹配管理器进行匹配 + return matcherManager.isConditionMatched(message, condition); } catch (Exception e) { log.error("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 条件匹配异常]", sceneRule.getId(), trigger, e); @@ -366,105 +351,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { } } - /** - * 匹配设备状态条件 - * - * @param message 设备消息 - * @param condition 触发条件 - * @return 是否匹配 - */ - private boolean matchDeviceStateCondition(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - // TODO @芋艿:需要根据设备状态进行匹配 - // 这里需要检查消息中的设备状态是否符合条件中定义的状态 - log.debug("[matchDeviceStateCondition][设备状态条件匹配逻辑待实现] condition: {}", condition); - return false; - } - - /** - * 匹配设备属性条件 - * - * @param message 设备消息 - * @param condition 触发条件 - * @return 是否匹配 - */ - private boolean matchDevicePropertyCondition(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - // 1. 检查标识符是否匹配 - String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); - if (StrUtil.isBlank(condition.getIdentifier()) || !condition.getIdentifier().equals(messageIdentifier)) { - return false; - } - - // 2. 获取消息中的属性值 - Object messageValue = message.getData(); - if (messageValue == null) { - return false; - } - - // 3. 根据操作符进行匹配 - return evaluateCondition(messageValue, condition.getOperator(), condition.getParam()); - } - - /** - * 匹配当前时间条件 - * - * @param condition 触发条件 - * @return 是否匹配 - */ - private boolean matchCurrentTimeCondition(IotSceneRuleDO.TriggerCondition condition) { - // TODO @芋艿:需要根据当前时间进行匹配 - // 这里需要检查当前时间是否符合条件中定义的时间范围 - log.debug("[matchCurrentTimeCondition][当前时间条件匹配逻辑待实现] condition: {}", condition); - return false; - } - - /** - * 评估条件是否匹配 - * - * @param sourceValue 源值(来自消息) - * @param operator 操作符 - * @param paramValue 参数值(来自条件配置) - * @return 是否匹配 - */ - private boolean evaluateCondition(Object sourceValue, String operator, String paramValue) { - try { - // 1. 校验操作符是否合法 - IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); - if (operatorEnum == null) { - log.warn("[evaluateCondition][存在错误的操作符({})]", operator); - return false; - } - - // 2.1 构建 Spring 表达式的变量 - Map springExpressionVariables = MapUtil.builder() - .put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue) - .build(); - // 2.2 根据操作符类型处理参数值 - if (StrUtil.isNotBlank(paramValue)) { - // TODO @puhui999:这里是不是在 IotSceneRuleConditionOperatorEnum 加个属性; - if (operatorEnum == IotSceneRuleConditionOperatorEnum.IN - || operatorEnum == IotSceneRuleConditionOperatorEnum.NOT_IN - || operatorEnum == IotSceneRuleConditionOperatorEnum.BETWEEN - || operatorEnum == IotSceneRuleConditionOperatorEnum.NOT_BETWEEN) { - // 处理多值情况 - List paramValues = StrUtil.split(paramValue, CharPool.COMMA); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, - convertList(paramValues, NumberUtil::parseDouble)); - } else { - // 处理单值情况 - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, - NumberUtil.parseDouble(paramValue)); - } - } - - // 3. 计算 Spring 表达式 - return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables); - } catch (Exception e) { - log.error("[evaluateCondition][条件评估异常] sourceValue: {}, operator: {}, paramValue: {}", - sourceValue, operator, paramValue, e); - return false; - } - } - // TODO @芋艿:【可优化】可以考虑增加下单测,边界太多了。 /** diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleMatcher.java similarity index 64% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleTriggerMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleMatcher.java index 2314bbc4f6..a77854ef96 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleMatcher.java @@ -15,14 +15,14 @@ import java.util.Map; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; /** - * IoT 场景规则触发器匹配器抽象基类 + * IoT 场景规则匹配器抽象基类 *

- * 提供通用的条件评估逻辑和工具方法 + * 提供通用的条件评估逻辑和工具方法,支持触发器和条件两种匹配类型 * * @author HUIHUI */ @Slf4j -public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRuleTriggerMatcher { +public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher { /** * 评估条件是否匹配 @@ -68,6 +68,8 @@ public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRule } } + // ========== 触发器相关工具方法 ========== + /** * 检查基础触发器参数是否有效 * @@ -79,15 +81,81 @@ public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRule } /** - * 检查操作符和值是否有效 + * 检查触发器操作符和值是否有效 * * @param trigger 触发器配置 * @return 是否有效 */ - protected boolean isOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { + protected boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue()); } + /** + * 记录触发器匹配成功日志 + * + * @param message 设备消息 + * @param trigger 触发器配置 + */ + protected void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType()); + } + + /** + * 记录触发器匹配失败日志 + * + * @param message 设备消息 + * @param trigger 触发器配置 + * @param reason 失败原因 + */ + protected void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { + log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason); + } + + // ========== 条件相关工具方法 ========== + + /** + * 检查基础条件参数是否有效 + * + * @param condition 触发条件 + * @return 是否有效 + */ + protected boolean isBasicConditionValid(IotSceneRuleDO.TriggerCondition condition) { + return condition != null && condition.getType() != null; + } + + /** + * 检查条件操作符和参数是否有效 + * + * @param condition 触发条件 + * @return 是否有效 + */ + protected boolean isConditionOperatorAndParamValid(IotSceneRuleDO.TriggerCondition condition) { + return StrUtil.isNotBlank(condition.getOperator()) && StrUtil.isNotBlank(condition.getParam()); + } + + /** + * 记录条件匹配成功日志 + * + * @param message 设备消息 + * @param condition 触发条件 + */ + protected void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { + log.debug("[{}][消息({}) 匹配条件({}) 成功]", getMatcherName(), message.getRequestId(), condition.getType()); + } + + /** + * 记录条件匹配失败日志 + * + * @param message 设备消息 + * @param condition 触发条件 + * @param reason 失败原因 + */ + protected void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) { + log.debug("[{}][消息({}) 匹配条件({}) 失败: {}]", getMatcherName(), message.getRequestId(), condition.getType(), reason); + } + + // ========== 通用工具方法 ========== + /** * 检查标识符是否匹配 * @@ -99,25 +167,4 @@ public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRule return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier); } - /** - * 记录匹配成功日志 - * - * @param message 设备消息 - * @param trigger 触发器配置 - */ - protected void logMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType()); - } - - /** - * 记录匹配失败日志 - * - * @param message 设备消息 - * @param trigger 触发器配置 - * @param reason 失败原因 - */ - protected void logMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { - log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason); - } - } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java new file mode 100644 index 0000000000..df11c666da --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java @@ -0,0 +1,177 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +import cn.hutool.core.util.StrUtil; +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.IotSceneRuleConditionLevelEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +/** + * 当前时间条件匹配器 + *

+ * 处理时间相关的子条件匹配逻辑 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher { + + /** + * 时间格式化器 - HH:mm:ss + */ + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); + + /** + * 时间格式化器 - HH:mm + */ + private static final DateTimeFormatter TIME_FORMATTER_SHORT = DateTimeFormatter.ofPattern("HH:mm"); + + @Override + public MatcherType getMatcherType() { + return MatcherType.CONDITION; + } + + @Override + public IotSceneRuleConditionTypeEnum getSupportedConditionType() { + return IotSceneRuleConditionTypeEnum.CURRENT_TIME; + } + + @Override + public IotSceneRuleConditionLevelEnum getSupportedConditionLevel() { + return IotSceneRuleConditionLevelEnum.SECONDARY; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { + // 1. 基础参数校验 + if (!isBasicConditionValid(condition)) { + logConditionMatchFailure(message, condition, "条件基础参数无效"); + return false; + } + + // 2. 检查操作符和参数是否有效 + if (!isConditionOperatorAndParamValid(condition)) { + logConditionMatchFailure(message, condition, "操作符或参数无效"); + return false; + } + + // 3. 获取当前时间 + LocalDateTime now = LocalDateTime.now(); + + // 4. 根据操作符类型进行不同的时间匹配 + String operator = condition.getOperator(); + String param = condition.getParam(); + + boolean matched = false; + + try { + if (operator.startsWith("date_time_")) { + // 日期时间匹配(时间戳) + matched = matchDateTime(now, operator, param); + } else if (operator.startsWith("time_")) { + // 当日时间匹配(HH:mm:ss) + matched = matchTime(now.toLocalTime(), operator, param); + } else { + // 其他操作符,使用通用条件评估器 + matched = evaluateCondition(now.toEpochSecond(java.time.ZoneOffset.of("+8")), operator, param); + } + + if (matched) { + logConditionMatchSuccess(message, condition); + } else { + logConditionMatchFailure(message, condition, "时间条件不匹配"); + } + + } catch (Exception e) { + log.error("[CurrentTimeConditionMatcher][时间条件匹配异常] operator: {}, param: {}", operator, param, e); + logConditionMatchFailure(message, condition, "时间条件匹配异常: " + e.getMessage()); + matched = false; + } + + return matched; + } + + /** + * 匹配日期时间(时间戳) + */ + private boolean matchDateTime(LocalDateTime now, String operator, String param) { + long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8")); + return evaluateCondition(currentTimestamp, operator.substring("date_time_".length()), param); + } + + /** + * 匹配当日时间(HH:mm:ss) + */ + private boolean matchTime(LocalTime currentTime, String operator, String param) { + try { + String actualOperator = operator.substring("time_".length()); + + if ("between".equals(actualOperator)) { + // 时间区间匹配 + String[] timeRange = param.split(","); + if (timeRange.length != 2) { + return false; + } + + LocalTime startTime = parseTime(timeRange[0].trim()); + LocalTime endTime = parseTime(timeRange[1].trim()); + + return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime); + } else { + // 单个时间比较 + LocalTime targetTime = parseTime(param); + + switch (actualOperator) { + case ">": + return currentTime.isAfter(targetTime); + case "<": + return currentTime.isBefore(targetTime); + case ">=": + return !currentTime.isBefore(targetTime); + case "<=": + return !currentTime.isAfter(targetTime); + case "=": + return currentTime.equals(targetTime); + default: + return false; + } + } + } catch (Exception e) { + log.error("[CurrentTimeConditionMatcher][时间解析异常] param: {}", param, e); + return false; + } + } + + /** + * 解析时间字符串 + */ + private LocalTime parseTime(String timeStr) { + if (StrUtil.isBlank(timeStr)) { + throw new IllegalArgumentException("时间字符串不能为空"); + } + + // 尝试不同的时间格式 + try { + if (timeStr.length() == 5) { // HH:mm + return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT); + } else { // HH:mm:ss + return LocalTime.parse(timeStr, TIME_FORMATTER); + } + } catch (Exception e) { + throw new IllegalArgumentException("时间格式无效: " + timeStr, e); + } + } + + @Override + public int getPriority() { + return 40; // 较低优先级 + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java index 1ee0cdda81..3c832f6553 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java @@ -16,13 +16,18 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { +public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher { /** * 设备事件上报消息方法 */ private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod(); + @Override + public MatcherType getMatcherType() { + return MatcherType.TRIGGER; + } + @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST; @@ -32,20 +37,20 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { // 1. 基础参数校验 if (!isBasicTriggerValid(trigger)) { - logMatchFailure(message, trigger, "触发器基础参数无效"); + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); return false; } // 2. 检查消息方法是否匹配 if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) { - logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod()); + logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod()); return false; } // 3. 检查标识符是否匹配 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { - logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); + logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); return false; } @@ -54,18 +59,18 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) { Object eventData = message.getData(); if (eventData == null) { - logMatchFailure(message, trigger, "消息中事件数据为空"); + logTriggerMatchFailure(message, trigger, "消息中事件数据为空"); return false; } boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); if (!matched) { - logMatchFailure(message, trigger, "事件数据条件不匹配"); + logTriggerMatchFailure(message, trigger, "事件数据条件不匹配"); return false; } } - logMatchSuccess(message, trigger); + logTriggerMatchSuccess(message, trigger); return true; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java new file mode 100644 index 0000000000..70c789edcd --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java @@ -0,0 +1,80 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +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.IotSceneRuleConditionLevelEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +import org.springframework.stereotype.Component; + +/** + * 设备属性条件匹配器 + *

+ * 处理设备属性相关的子条件匹配逻辑 + * + * @author HUIHUI + */ +@Component +public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher { + + @Override + public MatcherType getMatcherType() { + return MatcherType.CONDITION; + } + + @Override + public IotSceneRuleConditionTypeEnum getSupportedConditionType() { + return IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY; + } + + @Override + public IotSceneRuleConditionLevelEnum getSupportedConditionLevel() { + return IotSceneRuleConditionLevelEnum.SECONDARY; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { + // 1. 基础参数校验 + if (!isBasicConditionValid(condition)) { + logConditionMatchFailure(message, condition, "条件基础参数无效"); + return false; + } + + // 2. 检查标识符是否匹配 + String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); + if (!isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) { + logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier); + return false; + } + + // 3. 检查操作符和参数是否有效 + if (!isConditionOperatorAndParamValid(condition)) { + logConditionMatchFailure(message, condition, "操作符或参数无效"); + return false; + } + + // 4. 获取属性值 + Object propertyValue = message.getData(); + if (propertyValue == null) { + logConditionMatchFailure(message, condition, "消息中属性值为空"); + return false; + } + + // 5. 使用条件评估器进行匹配 + boolean matched = evaluateCondition(propertyValue, condition.getOperator(), condition.getParam()); + + if (matched) { + logConditionMatchSuccess(message, condition); + } else { + logConditionMatchFailure(message, condition, "设备属性条件不匹配"); + } + + return matched; + } + + @Override + public int getPriority() { + return 25; // 中等优先级 + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java index 7ed00519ec..0953453ed2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java @@ -15,13 +15,18 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { +public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatcher { /** * 设备属性上报消息方法 */ private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(); + @Override + public MatcherType getMatcherType() { + return MatcherType.TRIGGER; + } + @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST; @@ -31,33 +36,33 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { // 1. 基础参数校验 if (!isBasicTriggerValid(trigger)) { - logMatchFailure(message, trigger, "触发器基础参数无效"); + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); return false; } // 2. 检查消息方法是否匹配 if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) { - logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod()); + logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod()); return false; } // 3. 检查标识符是否匹配 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { - logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); + logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); return false; } // 4. 检查操作符和值是否有效 - if (!isOperatorAndValueValid(trigger)) { - logMatchFailure(message, trigger, "操作符或值无效"); + if (!isTriggerOperatorAndValueValid(trigger)) { + logTriggerMatchFailure(message, trigger, "操作符或值无效"); return false; } // 5. 获取属性值 Object propertyValue = message.getData(); if (propertyValue == null) { - logMatchFailure(message, trigger, "消息中属性值为空"); + logTriggerMatchFailure(message, trigger, "消息中属性值为空"); return false; } @@ -65,9 +70,9 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); if (matched) { - logMatchSuccess(message, trigger); + logTriggerMatchSuccess(message, trigger); } else { - logMatchFailure(message, trigger, "属性值条件不匹配"); + logTriggerMatchFailure(message, trigger, "属性值条件不匹配"); } return matched; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java index 996fe173f9..c2b7e4ef82 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java @@ -15,13 +15,18 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { +public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatcher { /** * 设备服务调用消息方法 */ private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(); + @Override + public MatcherType getMatcherType() { + return MatcherType.TRIGGER; + } + @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE; @@ -31,27 +36,27 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTrigg public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { // 1. 基础参数校验 if (!isBasicTriggerValid(trigger)) { - logMatchFailure(message, trigger, "触发器基础参数无效"); + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); return false; } // 2. 检查消息方法是否匹配 if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) { - logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod()); + logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod()); return false; } // 3. 检查标识符是否匹配 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { - logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); + logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); return false; } // 4. 对于服务调用触发器,通常只需要匹配服务标识符即可 // 不需要检查操作符和值,因为服务调用本身就是触发条件 - logMatchSuccess(message, trigger); + logTriggerMatchSuccess(message, trigger); return true; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java new file mode 100644 index 0000000000..aa3acab2a1 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +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.IotSceneRuleConditionLevelEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +import org.springframework.stereotype.Component; + +/** + * 设备状态条件匹配器 + *

+ * 处理设备状态相关的子条件匹配逻辑 + * + * @author HUIHUI + */ +@Component +public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher { + + @Override + public MatcherType getMatcherType() { + return MatcherType.CONDITION; + } + + @Override + public IotSceneRuleConditionTypeEnum getSupportedConditionType() { + return IotSceneRuleConditionTypeEnum.DEVICE_STATE; + } + + @Override + public IotSceneRuleConditionLevelEnum getSupportedConditionLevel() { + return IotSceneRuleConditionLevelEnum.SECONDARY; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { + // 1. 基础参数校验 + if (!isBasicConditionValid(condition)) { + logConditionMatchFailure(message, condition, "条件基础参数无效"); + return false; + } + + // 2. 检查操作符和参数是否有效 + if (!isConditionOperatorAndParamValid(condition)) { + logConditionMatchFailure(message, condition, "操作符或参数无效"); + return false; + } + + // 3. 获取设备状态值 + // 设备状态通常在消息的 data 字段中 + Object stateValue = message.getData(); + if (stateValue == null) { + logConditionMatchFailure(message, condition, "消息中设备状态值为空"); + return false; + } + + // 4. 使用条件评估器进行匹配 + boolean matched = evaluateCondition(stateValue, condition.getOperator(), condition.getParam()); + + if (matched) { + logConditionMatchSuccess(message, condition); + } else { + logConditionMatchFailure(message, condition, "设备状态条件不匹配"); + } + + return matched; + } + + @Override + public int getPriority() { + return 30; // 中等优先级 + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java index aec372c51b..a505e0d393 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java @@ -14,13 +14,18 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { +public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher { /** * 设备状态更新消息方法 */ private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(); + @Override + public MatcherType getMatcherType() { + return MatcherType.TRIGGER; + } + @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE; @@ -30,26 +35,26 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { // 1. 基础参数校验 if (!isBasicTriggerValid(trigger)) { - logMatchFailure(message, trigger, "触发器基础参数无效"); + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); return false; } // 2. 检查消息方法是否匹配 if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) { - logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod()); + logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod()); return false; } // 3. 检查操作符和值是否有效 - if (!isOperatorAndValueValid(trigger)) { - logMatchFailure(message, trigger, "操作符或值无效"); + if (!isTriggerOperatorAndValueValid(trigger)) { + logTriggerMatchFailure(message, trigger, "操作符或值无效"); return false; } // 4. 获取设备状态值 Object stateValue = message.getData(); if (stateValue == null) { - logMatchFailure(message, trigger, "消息中设备状态值为空"); + logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); return false; } @@ -57,9 +62,9 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); if (matched) { - logMatchSuccess(message, trigger); + logTriggerMatchSuccess(message, trigger); } else { - logMatchFailure(message, trigger, "状态值条件不匹配"); + logTriggerMatchFailure(message, trigger, "状态值条件不匹配"); } return matched; 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 new file mode 100644 index 0000000000..5e5c35baf5 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java @@ -0,0 +1,123 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +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.IotSceneRuleConditionLevelEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; + +/** + * IoT 场景规则匹配器统一接口 + *

+ * 支持触发器匹配和条件匹配两种类型,遵循策略模式设计 + *

+ * 匹配器类型说明: + * - 触发器匹配器:用于匹配主触发条件(如设备消息类型、定时器等) + * - 条件匹配器:用于匹配子条件(如设备状态、属性值、时间条件等) + * + * @author HUIHUI + */ +public interface IotSceneRuleMatcher { + + /** + * 匹配器类型枚举 + */ + enum MatcherType { + /** + * 触发器匹配器 - 用于匹配主触发条件 + */ + TRIGGER, + /** + * 条件匹配器 - 用于匹配子条件 + */ + CONDITION + } + + /** + * 获取匹配器类型 + * + * @return 匹配器类型 + */ + MatcherType getMatcherType(); + + /** + * 获取支持的触发器类型(仅触发器匹配器需要实现) + * + * @return 触发器类型枚举,条件匹配器返回 null + */ + default IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { + return null; + } + + /** + * 获取支持的条件类型(仅条件匹配器需要实现) + * + * @return 条件类型枚举,触发器匹配器返回 null + */ + default IotSceneRuleConditionTypeEnum getSupportedConditionType() { + return null; + } + + /** + * 获取支持的条件层级(仅条件匹配器需要实现) + * + * @return 条件层级枚举,触发器匹配器返回 null + */ + default IotSceneRuleConditionLevelEnum getSupportedConditionLevel() { + return null; + } + + /** + * 检查触发器是否匹配消息(仅触发器匹配器需要实现) + * + * @param message 设备消息 + * @param trigger 触发器配置 + * @return 是否匹配 + */ + default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + throw new UnsupportedOperationException("触发器匹配方法仅支持触发器匹配器"); + } + + /** + * 检查条件是否匹配消息(仅条件匹配器需要实现) + * + * @param message 设备消息 + * @param condition 触发条件 + * @return 是否匹配 + */ + default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { + throw new UnsupportedOperationException("条件匹配方法仅支持条件匹配器"); + } + + /** + * 获取匹配优先级(数值越小优先级越高) + *

+ * 用于在多个匹配器支持同一类型时确定优先级 + * + * @return 优先级数值 + */ + default int getPriority() { + return 100; + } + + /** + * 获取匹配器名称,用于日志和调试 + * + * @return 匹配器名称 + */ + default String getMatcherName() { + return this.getClass().getSimpleName(); + } + + /** + * 是否启用该匹配器 + *

+ * 可用于动态开关某些匹配器 + * + * @return 是否启用 + */ + default boolean isEnabled() { + return true; + } + +} 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 new file mode 100644 index 0000000000..9852e4acdb --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java @@ -0,0 +1,296 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; + +import cn.hutool.core.collection.CollUtil; +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.IotSceneRuleConditionLevelEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum.findTriggerTypeEnum; + +/** + * IoT 场景规则匹配器统一管理器 + *

+ * 负责管理所有匹配器(触发器匹配器和条件匹配器),并提供统一的匹配入口 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class IotSceneRuleMatcherManager { + + /** + * 触发器匹配器映射表 + * Key: 触发器类型枚举 + * Value: 对应的匹配器实例 + */ + private final Map triggerMatcherMap; + + /** + * 条件匹配器映射表 + * Key: 条件类型枚举 + * Value: 对应的匹配器实例 + */ + private final Map conditionMatcherMap; + + /** + * 所有匹配器列表(按优先级排序) + */ + private final List allMatchers; + + public IotSceneRuleMatcherManager(List matchers) { + if (CollUtil.isEmpty(matchers)) { + log.warn("[IotSceneRuleMatcherManager][没有找到任何匹配器]"); + this.triggerMatcherMap = new HashMap<>(); + this.conditionMatcherMap = new HashMap<>(); + this.allMatchers = new ArrayList<>(); + return; + } + + // 按优先级排序并过滤启用的匹配器 + this.allMatchers = matchers.stream() + .filter(IotSceneRuleMatcher::isEnabled) + .sorted(Comparator.comparing(IotSceneRuleMatcher::getPriority)) + .collect(Collectors.toList()); + + // 分离触发器匹配器和条件匹配器 + List triggerMatchers = this.allMatchers.stream() + .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.TRIGGER) + .toList(); + + List conditionMatchers = this.allMatchers.stream() + .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION) + .toList(); + + // 构建触发器匹配器映射表 + this.triggerMatcherMap = triggerMatchers.stream() + .collect(Collectors.toMap( + IotSceneRuleMatcher::getSupportedTriggerType, + Function.identity(), + (existing, replacement) -> { + log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]", + existing.getSupportedTriggerType(), + existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName()); + return existing.getPriority() <= replacement.getPriority() ? existing : replacement; + }, + LinkedHashMap::new + )); + + // 构建条件匹配器映射表 + this.conditionMatcherMap = conditionMatchers.stream() + .collect(Collectors.toMap( + IotSceneRuleMatcher::getSupportedConditionType, + Function.identity(), + (existing, replacement) -> { + log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]", + existing.getSupportedConditionType(), + existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName()); + return existing.getPriority() <= replacement.getPriority() ? existing : replacement; + }, + LinkedHashMap::new + )); + + log.info("[IotSceneRuleMatcherManager][初始化完成,共加载 {} 个匹配器,其中触发器匹配器 {} 个,条件匹配器 {} 个]", + this.allMatchers.size(), this.triggerMatcherMap.size(), this.conditionMatcherMap.size()); + + // 记录触发器匹配器详情 + this.triggerMatcherMap.forEach((type, matcher) -> + log.info("[IotSceneRuleMatcherManager][触发器匹配器] 类型: {}, 匹配器: {}, 优先级: {}", + type, matcher.getMatcherName(), matcher.getPriority())); + + // 记录条件匹配器详情 + this.conditionMatcherMap.forEach((type, matcher) -> + log.info("[IotSceneRuleMatcherManager][条件匹配器] 类型: {}, 匹配器: {}, 优先级: {}, 层级: {}", + type, matcher.getMatcherName(), matcher.getPriority(), matcher.getSupportedConditionLevel())); + } + + /** + * 检查触发器是否匹配消息(主条件匹配) + * + * @param message 设备消息 + * @param trigger 触发器配置 + * @return 是否匹配 + */ + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + if (message == null || trigger == null || trigger.getType() == null) { + log.debug("[isMatched][参数无效] message: {}, trigger: {}", message, trigger); + return false; + } + + // 根据触发器类型查找对应的匹配器 + IotSceneRuleTriggerTypeEnum triggerType = findTriggerTypeEnum(trigger.getType()); + if (triggerType == null) { + log.warn("[isMatched][未知的触发器类型: {}]", trigger.getType()); + return false; + } + + IotSceneRuleMatcher matcher = triggerMatcherMap.get(triggerType); + if (matcher == null) { + log.warn("[isMatched][触发器类型({})没有对应的匹配器]", triggerType); + return false; + } + + try { + return matcher.isMatched(message, trigger); + } catch (Exception e) { + log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}, matcher: {}", + message, trigger, matcher.getMatcherName(), e); + return false; + } + } + + /** + * 检查子条件是否匹配消息 + * + * @param message 设备消息 + * @param condition 触发条件 + * @return 是否匹配 + */ + public boolean isConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { + if (message == null || condition == null || condition.getType() == null) { + log.debug("[isConditionMatched][参数无效] message: {}, condition: {}", message, condition); + return false; + } + + // 根据条件类型查找对应的匹配器 + IotSceneRuleConditionTypeEnum conditionType = findConditionTypeEnum(condition.getType()); + if (conditionType == null) { + log.warn("[isConditionMatched][未知的条件类型: {}]", condition.getType()); + return false; + } + + IotSceneRuleMatcher matcher = conditionMatcherMap.get(conditionType); + if (matcher == null) { + log.warn("[isConditionMatched][条件类型({})没有对应的匹配器]", conditionType); + return false; + } + + try { + return matcher.isMatched(message, condition); + } catch (Exception e) { + log.error("[isConditionMatched][条件匹配异常] message: {}, condition: {}, matcher: {}", + message, condition, matcher.getMatcherName(), e); + return false; + } + } + + /** + * 根据类型值查找条件类型枚举 + * + * @param typeValue 类型值 + * @return 条件类型枚举 + */ + private IotSceneRuleConditionTypeEnum findConditionTypeEnum(Integer typeValue) { + return Arrays.stream(IotSceneRuleConditionTypeEnum.values()) + .filter(type -> type.getType().equals(typeValue)) + .findFirst() + .orElse(null); + } + + /** + * 获取所有支持的触发器类型 + * + * @return 支持的触发器类型列表 + */ + public Set getSupportedTriggerTypes() { + return new HashSet<>(triggerMatcherMap.keySet()); + } + + /** + * 获取所有支持的条件类型 + * + * @return 支持的条件类型列表 + */ + public Set getSupportedConditionTypes() { + return new HashSet<>(conditionMatcherMap.keySet()); + } + + /** + * 获取指定触发器类型的匹配器 + * + * @param triggerType 触发器类型 + * @return 匹配器实例,如果不存在则返回 null + */ + public IotSceneRuleMatcher getTriggerMatcher(IotSceneRuleTriggerTypeEnum triggerType) { + return triggerMatcherMap.get(triggerType); + } + + /** + * 获取指定条件类型的匹配器 + * + * @param conditionType 条件类型 + * @return 匹配器实例,如果不存在则返回 null + */ + public IotSceneRuleMatcher getConditionMatcher(IotSceneRuleConditionTypeEnum conditionType) { + return conditionMatcherMap.get(conditionType); + } + + /** + * 根据条件层级获取匹配器列表 + * + * @param level 条件层级 + * @return 匹配器列表 + */ + public List getMatchersByLevel(IotSceneRuleConditionLevelEnum level) { + return allMatchers.stream() + .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION) + .filter(matcher -> matcher.getSupportedConditionLevel() == level) + .collect(Collectors.toList()); + } + + /** + * 获取所有匹配器的统计信息 + * + * @return 统计信息映射表 + */ + public Map getMatcherStatistics() { + Map statistics = new HashMap<>(); + statistics.put("totalMatchers", allMatchers.size()); + statistics.put("triggerMatchers", triggerMatcherMap.size()); + statistics.put("conditionMatchers", conditionMatcherMap.size()); + statistics.put("supportedTriggerTypes", getSupportedTriggerTypes()); + statistics.put("supportedConditionTypes", getSupportedConditionTypes()); + + // 按层级统计条件匹配器 + Map levelStats = allMatchers.stream() + .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION) + .collect(Collectors.groupingBy( + IotSceneRuleMatcher::getSupportedConditionLevel, + Collectors.counting() + )); + statistics.put("conditionLevelStatistics", levelStats); + + // 触发器匹配器详情 + Map triggerMatcherDetails = new HashMap<>(); + triggerMatcherMap.forEach((type, matcher) -> { + Map detail = new HashMap<>(); + detail.put("matcherName", matcher.getMatcherName()); + detail.put("priority", matcher.getPriority()); + detail.put("enabled", matcher.isEnabled()); + triggerMatcherDetails.put(type.name(), detail); + }); + statistics.put("triggerMatcherDetails", triggerMatcherDetails); + + // 条件匹配器详情 + Map conditionMatcherDetails = new HashMap<>(); + conditionMatcherMap.forEach((type, matcher) -> { + Map detail = new HashMap<>(); + detail.put("matcherName", matcher.getMatcherName()); + detail.put("priority", matcher.getPriority()); + detail.put("level", matcher.getSupportedConditionLevel()); + detail.put("enabled", matcher.isEnabled()); + conditionMatcherDetails.put(type.name(), detail); + }); + statistics.put("conditionMatcherDetails", conditionMatcherDetails); + + return statistics; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java deleted file mode 100644 index bf111a2663..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java +++ /dev/null @@ -1,63 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; - -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; - -/** - * IoT 场景规则触发器匹配策略接口 - *

- * 用于实现不同类型触发器的匹配逻辑,遵循策略模式设计 - * - * @author HUIHUI - */ -public interface IotSceneRuleTriggerMatcher { - - /** - * 获取支持的触发器类型 - * - * @return 触发器类型枚举 - */ - IotSceneRuleTriggerTypeEnum getSupportedTriggerType(); - - /** - * 检查触发器是否匹配消息 - * - * @param message 设备消息 - * @param trigger 触发器配置 - * @return 是否匹配 - */ - boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger); - - /** - * 获取匹配优先级(数值越小优先级越高) - *

- * 用于在多个匹配器支持同一触发器类型时确定优先级 - * - * @return 优先级数值 - */ - default int getPriority() { - return 100; - } - - /** - * 获取匹配器名称,用于日志和调试 - * - * @return 匹配器名称 - */ - default String getMatcherName() { - return this.getClass().getSimpleName(); - } - - /** - * 是否启用该匹配器 - *

- * 可用于动态开关某些匹配器 - * - * @return 是否启用 - */ - default boolean isEnabled() { - return true; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java deleted file mode 100644 index 6e6c383a95..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java +++ /dev/null @@ -1,151 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; - -import cn.hutool.core.collection.CollUtil; -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 lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum.findTriggerTypeEnum; - -/** - * IoT 场景规则触发器匹配管理器 - *

- * 负责管理所有触发器匹配器,并提供统一的匹配入口 - * - * @author HUIHUI - */ -@Component -@Slf4j -public class IotSceneRuleTriggerMatcherManager { - - /** - * 触发器匹配器映射表 - * Key: 触发器类型枚举 - * Value: 对应的匹配器实例 - */ - private final Map matcherMap; - - /** - * 所有匹配器列表(按优先级排序) - */ - private final List allMatchers; - - public IotSceneRuleTriggerMatcherManager(List matchers) { - if (CollUtil.isEmpty(matchers)) { - log.warn("[IotSceneRuleTriggerMatcherManager][没有找到任何触发器匹配器]"); - this.matcherMap = new HashMap<>(); - this.allMatchers = new ArrayList<>(); - return; - } - - // 按优先级排序并过滤启用的匹配器 - this.allMatchers = matchers.stream() - .filter(IotSceneRuleTriggerMatcher::isEnabled) - .sorted(Comparator.comparing(IotSceneRuleTriggerMatcher::getPriority)) - .collect(Collectors.toList()); - - // 构建匹配器映射表 - this.matcherMap = this.allMatchers.stream() - .collect(Collectors.toMap( - IotSceneRuleTriggerMatcher::getSupportedTriggerType, - Function.identity(), - (existing, replacement) -> { - log.warn("[IotSceneRuleTriggerMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]", - existing.getSupportedTriggerType(), - existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName()); - return existing.getPriority() <= replacement.getPriority() ? existing : replacement; - }, - LinkedHashMap::new - )); - - log.info("[IotSceneRuleTriggerMatcherManager][初始化完成,共加载 {} 个触发器匹配器]", this.matcherMap.size()); - this.matcherMap.forEach((type, matcher) -> - log.info("[IotSceneRuleTriggerMatcherManager][触发器类型: {}, 匹配器: {}, 优先级: {}]", - type, matcher.getMatcherName(), matcher.getPriority())); - } - - /** - * 检查触发器是否匹配消息 - * - * @param message 设备消息 - * @param trigger 触发器配置 - * @return 是否匹配 - */ - public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - if (message == null || trigger == null || trigger.getType() == null) { - log.debug("[isMatched][参数无效] message: {}, trigger: {}", message, trigger); - return false; - } - - // 根据触发器类型查找对应的匹配器 - IotSceneRuleTriggerTypeEnum triggerType = findTriggerTypeEnum(trigger.getType()); - if (triggerType == null) { - log.warn("[isMatched][未知的触发器类型: {}]", trigger.getType()); - return false; - } - - IotSceneRuleTriggerMatcher matcher = matcherMap.get(triggerType); - if (matcher == null) { - log.warn("[isMatched][触发器类型({})没有对应的匹配器]", triggerType); - return false; - } - - try { - return matcher.isMatched(message, trigger); - } catch (Exception e) { - log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}, matcher: {}", - message, trigger, matcher.getMatcherName(), e); - return false; - } - } - - /** - * 获取所有支持的触发器类型 - * - * @return 支持的触发器类型列表 - */ - public Set getSupportedTriggerTypes() { - return new HashSet<>(matcherMap.keySet()); - } - - /** - * 获取指定触发器类型的匹配器 - * - * @param triggerType 触发器类型 - * @return 匹配器实例,如果不存在则返回 null - */ - public IotSceneRuleTriggerMatcher getMatcher(IotSceneRuleTriggerTypeEnum triggerType) { - return matcherMap.get(triggerType); - } - - /** - * 获取所有匹配器的统计信息 - * - * @return 统计信息映射表 - */ - public Map getMatcherStatistics() { - Map statistics = new HashMap<>(); - statistics.put("totalMatchers", allMatchers.size()); - statistics.put("enabledMatchers", matcherMap.size()); - statistics.put("supportedTriggerTypes", getSupportedTriggerTypes()); - - Map matcherDetails = new HashMap<>(); - matcherMap.forEach((type, matcher) -> { - Map detail = new HashMap<>(); - detail.put("matcherName", matcher.getMatcherName()); - detail.put("priority", matcher.getPriority()); - detail.put("enabled", matcher.isEnabled()); - matcherDetails.put(type.name(), detail); - }); - statistics.put("matcherDetails", matcherDetails); - - return statistics; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java index c37a10a13f..a5d536cb8f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java @@ -15,7 +15,12 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class TimerTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { +public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher { + + @Override + public MatcherType getMatcherType() { + return MatcherType.TRIGGER; + } @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { @@ -26,13 +31,13 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { // 1. 基础参数校验 if (!isBasicTriggerValid(trigger)) { - logMatchFailure(message, trigger, "触发器基础参数无效"); + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); return false; } // 2. 检查 CRON 表达式是否存在 if (StrUtil.isBlank(trigger.getCronExpression())) { - logMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); + logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); return false; } @@ -41,11 +46,11 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { // 4. 可以添加 CRON 表达式格式验证 if (!isValidCronExpression(trigger.getCronExpression())) { - logMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); + logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); return false; } - logMatchSuccess(message, trigger); + logTriggerMatchSuccess(message, trigger); return true; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java index 9903e8cc2f..7483182566 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java @@ -20,13 +20,12 @@ import static org.junit.jupiter.api.Assertions.*; */ public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { - private IotSceneRuleTriggerMatcherManager matcherManager; - private List matchers; + private IotSceneRuleMatcherManager matcherManager; @BeforeEach void setUp() { // 创建所有匹配器实例 - matchers = Arrays.asList( + List matchers = Arrays.asList( new DeviceStateUpdateTriggerMatcher(), new DevicePropertyPostTriggerMatcher(), new DeviceEventPostTriggerMatcher(), @@ -35,7 +34,7 @@ public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { ); // 初始化匹配器管理器 - matcherManager = new IotSceneRuleTriggerMatcherManager(matchers); + matcherManager = new IotSceneRuleMatcherManager(matchers); } @Test @@ -197,4 +196,5 @@ public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE)); assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.TIMER)); } + } From 378cf1e997611e9330ab33ba8244f65b2bace0a3 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 15 Aug 2025 17:35:01 +0800 Subject: [PATCH 7/8] =?UTF-8?q?perf:=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=A7=84=E5=88=99=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rule/IotSceneRuleConditionLevelEnum.java | 74 ------------------- .../matcher/CurrentTimeConditionMatcher.java | 6 -- .../DevicePropertyConditionMatcher.java | 6 -- .../matcher/DeviceStateConditionMatcher.java | 6 -- .../scene/matcher/IotSceneRuleMatcher.java | 10 --- .../matcher/IotSceneRuleMatcherManager.java | 28 +------ 6 files changed, 2 insertions(+), 128 deletions(-) delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionLevelEnum.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionLevelEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionLevelEnum.java deleted file mode 100644 index c83b72c1f5..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionLevelEnum.java +++ /dev/null @@ -1,74 +0,0 @@ -package cn.iocoder.yudao.module.iot.enums.rule; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * IoT 场景规则条件层级枚举 - *

- * 用于区分主条件(触发器级别)和子条件(条件分组级别) - * - * @author HUIHUI - */ -@AllArgsConstructor -@Getter -public enum IotSceneRuleConditionLevelEnum { - - /** - * 主条件 - 触发器级别的条件 - * 用于判断触发器本身是否匹配(如消息类型、设备标识等) - */ - PRIMARY(1, "主条件"), - - /** - * 子条件 - 条件分组级别的条件 - * 用于判断具体的业务条件(如设备状态、属性值、时间条件等) - */ - SECONDARY(2, "子条件"); - - /** - * 条件层级 - */ - private final Integer level; - - /** - * 条件层级名称 - */ - private final String name; - - /** - * 根据层级值获取枚举 - * - * @param level 层级值 - * @return 条件层级枚举 - */ - public static IotSceneRuleConditionLevelEnum levelOf(Integer level) { - if (level == null) { - return null; - } - for (IotSceneRuleConditionLevelEnum levelEnum : values()) { - if (levelEnum.getLevel().equals(level)) { - return levelEnum; - } - } - return null; - } - - /** - * 判断是否为主条件 - * - * @return 是否为主条件 - */ - public boolean isPrimary() { - return this == PRIMARY; - } - - /** - * 判断是否为子条件 - * - * @return 是否为子条件 - */ - public boolean isSecondary() { - return this == SECONDARY; - } -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java index df11c666da..ae6c8f671d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; import cn.hutool.core.util.StrUtil; 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.IotSceneRuleConditionLevelEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -43,11 +42,6 @@ public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher { return IotSceneRuleConditionTypeEnum.CURRENT_TIME; } - @Override - public IotSceneRuleConditionLevelEnum getSupportedConditionLevel() { - return IotSceneRuleConditionLevelEnum.SECONDARY; - } - @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { // 1. 基础参数校验 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java index 70c789edcd..ed8e12d6c6 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; 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.IotSceneRuleConditionLevelEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; import org.springframework.stereotype.Component; @@ -27,11 +26,6 @@ public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher return IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY; } - @Override - public IotSceneRuleConditionLevelEnum getSupportedConditionLevel() { - return IotSceneRuleConditionLevelEnum.SECONDARY; - } - @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { // 1. 基础参数校验 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java index aa3acab2a1..f946e499c8 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; 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.IotSceneRuleConditionLevelEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; import org.springframework.stereotype.Component; @@ -26,11 +25,6 @@ public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher { return IotSceneRuleConditionTypeEnum.DEVICE_STATE; } - @Override - public IotSceneRuleConditionLevelEnum getSupportedConditionLevel() { - return IotSceneRuleConditionLevelEnum.SECONDARY; - } - @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { // 1. 基础参数校验 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 5e5c35baf5..cb12384647 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 @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; 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.IotSceneRuleConditionLevelEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; @@ -58,15 +57,6 @@ public interface IotSceneRuleMatcher { return null; } - /** - * 获取支持的条件层级(仅条件匹配器需要实现) - * - * @return 条件层级枚举,触发器匹配器返回 null - */ - default IotSceneRuleConditionLevelEnum getSupportedConditionLevel() { - return null; - } - /** * 检查触发器是否匹配消息(仅触发器匹配器需要实现) * 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 9852e4acdb..7c45a6ca6b 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 @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; import cn.hutool.core.collection.CollUtil; 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.IotSceneRuleConditionLevelEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; import lombok.extern.slf4j.Slf4j; @@ -107,8 +106,8 @@ public class IotSceneRuleMatcherManager { // 记录条件匹配器详情 this.conditionMatcherMap.forEach((type, matcher) -> - log.info("[IotSceneRuleMatcherManager][条件匹配器] 类型: {}, 匹配器: {}, 优先级: {}, 层级: {}", - type, matcher.getMatcherName(), matcher.getPriority(), matcher.getSupportedConditionLevel())); + log.info("[IotSceneRuleMatcherManager][条件匹配器] 类型: {}, 匹配器: {}, 优先级: {}", + type, matcher.getMatcherName(), matcher.getPriority())); } /** @@ -232,19 +231,6 @@ public class IotSceneRuleMatcherManager { return conditionMatcherMap.get(conditionType); } - /** - * 根据条件层级获取匹配器列表 - * - * @param level 条件层级 - * @return 匹配器列表 - */ - public List getMatchersByLevel(IotSceneRuleConditionLevelEnum level) { - return allMatchers.stream() - .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION) - .filter(matcher -> matcher.getSupportedConditionLevel() == level) - .collect(Collectors.toList()); - } - /** * 获取所有匹配器的统计信息 * @@ -258,15 +244,6 @@ public class IotSceneRuleMatcherManager { statistics.put("supportedTriggerTypes", getSupportedTriggerTypes()); statistics.put("supportedConditionTypes", getSupportedConditionTypes()); - // 按层级统计条件匹配器 - Map levelStats = allMatchers.stream() - .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION) - .collect(Collectors.groupingBy( - IotSceneRuleMatcher::getSupportedConditionLevel, - Collectors.counting() - )); - statistics.put("conditionLevelStatistics", levelStats); - // 触发器匹配器详情 Map triggerMatcherDetails = new HashMap<>(); triggerMatcherMap.forEach((type, matcher) -> { @@ -284,7 +261,6 @@ public class IotSceneRuleMatcherManager { Map detail = new HashMap<>(); detail.put("matcherName", matcher.getMatcherName()); detail.put("priority", matcher.getPriority()); - detail.put("level", matcher.getSupportedConditionLevel()); detail.put("enabled", matcher.isEnabled()); conditionMatcherDetails.put(type.name(), detail); }); From a328dcf172f25cbf39efcc9dcc070c5a2af8034b Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 15 Aug 2025 17:51:26 +0800 Subject: [PATCH 8/8] =?UTF-8?q?perf:=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E4=BC=98=E5=8C=96=20IotRedisRuleAction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/IotAbstractDataSinkConfig.java | 2 +- .../rule/config/IotDataSinkRedisConfig.java | 64 ++++++ .../config/IotDataSinkRedisStreamConfig.java | 34 ---- .../iot/enums/rule/IotDataSinkTypeEnum.java | 2 +- .../enums/rule/IotRedisDataStructureEnum.java | 36 ++++ .../rule/data/action/IotRedisRuleAction.java | 182 ++++++++++++++++++ .../data/action/IotRedisStreamRuleAction.java | 81 -------- .../databridge/IotDataBridgeExecuteTest.java | 21 +- 8 files changed, 295 insertions(+), 127 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisStreamConfig.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRedisDataStructureEnum.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisRuleAction.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisStreamRuleAction.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 4d08d43410..68a8fd699b 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 @@ -18,7 +18,7 @@ import lombok.Data; @JsonSubTypes({ @JsonSubTypes.Type(value = IotDataSinkHttpConfig.class, name = "1"), @JsonSubTypes.Type(value = IotDataSinkMqttConfig.class, name = "10"), - @JsonSubTypes.Type(value = IotDataSinkRedisStreamConfig.class, name = "21"), + @JsonSubTypes.Type(value = IotDataSinkRedisConfig.class, name = "21"), @JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"), @JsonSubTypes.Type(value = IotDataSinkRabbitMQConfig.class, name = "31"), @JsonSubTypes.Type(value = IotDataSinkKafkaConfig.class, name = "32"), diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java new file mode 100644 index 0000000000..07460ac368 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotRedisDataStructureEnum; +import lombok.Data; + +/** + * IoT Redis 配置 {@link IotAbstractDataSinkConfig} 实现类 + * + * @author HUIHUI + */ +@Data +public class IotDataSinkRedisConfig extends IotAbstractDataSinkConfig { + + /** + * Redis 服务器地址 + */ + private String host; + /** + * 端口 + */ + private Integer port; + /** + * 密码 + */ + private String password; + /** + * 数据库索引 + */ + private Integer database; + + /** + * Redis 数据结构类型 + *

+ * 枚举 {@link IotRedisDataStructureEnum} + */ + @InEnum(IotRedisDataStructureEnum.class) + private Integer dataStructure; + + /** + * 主题/键名 + *

+ * 对于不同的数据结构: + * - Stream: 流的键名 + * - Hash: Hash 的键名 + * - List: 列表的键名 + * - Set: 集合的键名 + * - ZSet: 有序集合的键名 + * - String: 字符串的键名 + */ + private String topic; + + /** + * Hash 字段名(仅当 dataStructure 为 HASH 时使用) + */ + private String hashField; + + /** + * ZSet 分数字段(仅当 dataStructure 为 ZSET 时使用) + * 指定消息中哪个字段作为分数,如果不指定则使用当前时间戳 + */ + private String scoreField; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisStreamConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisStreamConfig.java deleted file mode 100644 index 4df0ad7c38..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisStreamConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config; - -import lombok.Data; - -/** - * IoT Redis Stream 配置 {@link IotAbstractDataSinkConfig} 实现类 - * - * @author HUIHUI - */ -@Data -public class IotDataSinkRedisStreamConfig extends IotAbstractDataSinkConfig { - - /** - * Redis 服务器地址 - */ - private String host; - /** - * 端口 - */ - private Integer port; - /** - * 密码 - */ - private String password; - /** - * 数据库索引 - */ - private Integer database; - - /** - * 主题 - */ - private String topic; -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java index 33b3558775..45a557db61 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java @@ -22,7 +22,7 @@ public enum IotDataSinkTypeEnum implements ArrayValuable { MQTT(10, "MQTT"), // TODO 待实现; DATABASE(20, "Database"), // TODO @puhui999:待实现;可以简单点,对应的表名是什么,字段先固定了。 - REDIS_STREAM(21, "Redis Stream"), // TODO @puhui999:改成 Redis;然后枚举不同的数据结构?这样,枚举就可以是 Redis 了 + REDIS(21, "Redis"), ROCKETMQ(30, "RocketMQ"), RABBITMQ(31, "RabbitMQ"), diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRedisDataStructureEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRedisDataStructureEnum.java new file mode 100644 index 0000000000..4195b08439 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRedisDataStructureEnum.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.iot.enums.rule; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * IoT Redis 数据结构类型枚举 + * + * @author HUIHUI + */ +@RequiredArgsConstructor +@Getter +public enum IotRedisDataStructureEnum implements ArrayValuable { + + STREAM(1, "Stream"), + HASH(2, "Hash"), + LIST(3, "List"), + SET(4, "Set"), + ZSET(5, "ZSet"), + STRING(6, "String"); + + private final Integer type; + + private final String name; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRedisDataStructureEnum::getType).toArray(Integer[]::new); + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisRuleAction.java new file mode 100644 index 0000000000..51abffee3b --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisRuleAction.java @@ -0,0 +1,182 @@ +package cn.iocoder.yudao.module.iot.service.rule.data.action; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkRedisConfig; +import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotRedisDataStructureEnum; +import lombok.extern.slf4j.Slf4j; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.SingleServerConfig; +import org.redisson.spring.data.connection.RedissonConnectionFactory; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.stream.ObjectRecord; +import org.springframework.data.redis.connection.stream.StreamRecords; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * Redis 的 {@link IotDataRuleAction} 实现类 + * 支持多种 Redis 数据结构:Stream、Hash、List、Set、ZSet、String + * + * @author HUIHUI + */ +@Component +@Slf4j +public class IotRedisRuleAction extends + IotDataRuleCacheableAction> { + + @Override + public Integer getType() { + return IotDataSinkTypeEnum.REDIS.getType(); + } + + @Override + public void execute(IotDeviceMessage message, IotDataSinkRedisConfig config) throws Exception { + // 1. 获取 RedisTemplate + RedisTemplate redisTemplate = getProducer(config); + + // 2. 根据数据结构类型执行不同的操作 + String messageJson = JsonUtils.toJsonString(message); + IotRedisDataStructureEnum dataStructure = getDataStructureByType(config.getDataStructure()); + + switch (dataStructure) { + case STREAM: + executeStream(redisTemplate, config, messageJson); + break; + case HASH: + executeHash(redisTemplate, config, message, messageJson); + break; + case LIST: + executeList(redisTemplate, config, messageJson); + break; + case SET: + executeSet(redisTemplate, config, messageJson); + break; + case ZSET: + executeZSet(redisTemplate, config, message, messageJson); + break; + case STRING: + executeString(redisTemplate, config, messageJson); + break; + default: + throw new IllegalArgumentException("不支持的 Redis 数据结构类型: " + dataStructure); + } + + log.info("[execute][消息发送成功] dataStructure: {}, config: {}", dataStructure.getName(), config); + } + + /** + * 执行 Stream 操作 + */ + private void executeStream(RedisTemplate redisTemplate, IotDataSinkRedisConfig config, String messageJson) { + ObjectRecord record = StreamRecords.newRecord() + .ofObject(messageJson).withStreamKey(config.getTopic()); + redisTemplate.opsForStream().add(record); + } + + /** + * 执行 Hash 操作 + */ + private void executeHash(RedisTemplate redisTemplate, IotDataSinkRedisConfig config, + IotDeviceMessage message, String messageJson) { + String hashField = StrUtil.isNotBlank(config.getHashField()) ? + config.getHashField() : String.valueOf(message.getDeviceId()); + redisTemplate.opsForHash().put(config.getTopic(), hashField, messageJson); + } + + /** + * 执行 List 操作 + */ + private void executeList(RedisTemplate redisTemplate, IotDataSinkRedisConfig config, String messageJson) { + redisTemplate.opsForList().rightPush(config.getTopic(), messageJson); + } + + /** + * 执行 Set 操作 + */ + private void executeSet(RedisTemplate redisTemplate, IotDataSinkRedisConfig config, String messageJson) { + redisTemplate.opsForSet().add(config.getTopic(), messageJson); + } + + /** + * 执行 ZSet 操作 + */ + private void executeZSet(RedisTemplate redisTemplate, IotDataSinkRedisConfig config, + IotDeviceMessage message, String messageJson) { + double score; + if (StrUtil.isNotBlank(config.getScoreField())) { + // 尝试从消息中获取分数字段 + try { + Map messageMap = JsonUtils.parseObject(messageJson, Map.class); + Object scoreValue = messageMap.get(config.getScoreField()); + score = scoreValue instanceof Number ? ((Number) scoreValue).doubleValue() : System.currentTimeMillis(); + } catch (Exception e) { + score = System.currentTimeMillis(); + } + } else { + // 使用当前时间戳作为分数 + score = System.currentTimeMillis(); + } + redisTemplate.opsForZSet().add(config.getTopic(), messageJson, score); + } + + /** + * 执行 String 操作 + */ + private void executeString(RedisTemplate redisTemplate, IotDataSinkRedisConfig config, String messageJson) { + redisTemplate.opsForValue().set(config.getTopic(), messageJson); + } + + @Override + protected RedisTemplate initProducer(IotDataSinkRedisConfig config) { + // 1.1 创建 Redisson 配置 + Config redissonConfig = new Config(); + SingleServerConfig serverConfig = redissonConfig.useSingleServer() + .setAddress("redis://" + config.getHost() + ":" + config.getPort()) + .setDatabase(config.getDatabase()); + // 1.2 设置密码(如果有) + if (StrUtil.isNotBlank(config.getPassword())) { + serverConfig.setPassword(config.getPassword()); + } + + // 2.1 创建 RedisTemplate 并配置 + RedissonClient redisson = Redisson.create(redissonConfig); + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(new RedissonConnectionFactory(redisson)); + // 2.2 设置序列化器 + template.setKeySerializer(RedisSerializer.string()); + template.setHashKeySerializer(RedisSerializer.string()); + template.setValueSerializer(RedisSerializer.json()); + template.setHashValueSerializer(RedisSerializer.json()); + template.afterPropertiesSet(); + return template; + } + + @Override + protected void closeProducer(RedisTemplate producer) throws Exception { + RedisConnectionFactory factory = producer.getConnectionFactory(); + if (factory != null) { + ((RedissonConnectionFactory) factory).destroy(); + } + } + + /** + * 根据类型值获取数据结构枚举 + */ + private IotRedisDataStructureEnum getDataStructureByType(Integer type) { + for (IotRedisDataStructureEnum dataStructure : IotRedisDataStructureEnum.values()) { + if (dataStructure.getType().equals(type)) { + return dataStructure; + } + } + throw new IllegalArgumentException("不支持的 Redis 数据结构类型: " + type); + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisStreamRuleAction.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisStreamRuleAction.java deleted file mode 100644 index d3bb81c8e9..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisStreamRuleAction.java +++ /dev/null @@ -1,81 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.data.action; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkRedisStreamConfig; -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum; -import lombok.extern.slf4j.Slf4j; -import org.redisson.Redisson; -import org.redisson.api.RedissonClient; -import org.redisson.config.Config; -import org.redisson.config.SingleServerConfig; -import org.redisson.spring.data.connection.RedissonConnectionFactory; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.stream.ObjectRecord; -import org.springframework.data.redis.connection.stream.StreamRecords; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.stereotype.Component; - -/** - * Redis Stream 的 {@link IotDataRuleAction} 实现类 - * - * @author HUIHUI - */ -@Component -@Slf4j -public class IotRedisStreamRuleAction extends - IotDataRuleCacheableAction> { - - @Override - public Integer getType() { - return IotDataSinkTypeEnum.REDIS_STREAM.getType(); - } - - @Override - public void execute(IotDeviceMessage message, IotDataSinkRedisStreamConfig config) throws Exception { - // 1. 获取 RedisTemplate - RedisTemplate redisTemplate = getProducer(config); - - // 2. 创建并发送 Stream 记录 - ObjectRecord record = StreamRecords.newRecord() - .ofObject(JsonUtils.toJsonString(message)).withStreamKey(config.getTopic()); - String recordId = String.valueOf(redisTemplate.opsForStream().add(record)); - log.info("[execute][消息发送成功] messageId: {}, config: {}", recordId, config); - } - - @Override - protected RedisTemplate initProducer(IotDataSinkRedisStreamConfig config) { - // 1.1 创建 Redisson 配置 - Config redissonConfig = new Config(); - SingleServerConfig serverConfig = redissonConfig.useSingleServer() - .setAddress("redis://" + config.getHost() + ":" + config.getPort()) - .setDatabase(config.getDatabase()); - // 1.2 设置密码(如果有) - if (StrUtil.isNotBlank(config.getPassword())) { - serverConfig.setPassword(config.getPassword()); - } - - // 2.1 创建 RedisTemplate 并配置 - RedissonClient redisson = Redisson.create(redissonConfig); - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(new RedissonConnectionFactory(redisson)); - // 2.2 设置序列化器 - template.setKeySerializer(RedisSerializer.string()); - template.setHashKeySerializer(RedisSerializer.string()); - template.setValueSerializer(RedisSerializer.json()); - template.setHashValueSerializer(RedisSerializer.json()); - template.afterPropertiesSet(); - return template; - } - - @Override - protected void closeProducer(RedisTemplate producer) throws Exception { - RedisConnectionFactory factory = producer.getConnectionFactory(); - if (factory != null) { - ((RedissonConnectionFactory) factory).destroy(); - } - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotDataBridgeExecuteTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotDataBridgeExecuteTest.java index 5394008022..055ccb01b2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotDataBridgeExecuteTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotDataBridgeExecuteTest.java @@ -85,20 +85,21 @@ public class IotDataBridgeExecuteTest extends BaseMockitoUnitTest { } @Test - public void testRedisStreamDataBridge() throws Exception { + public void testRedisDataBridge() throws Exception { // 1. 创建执行器实例 - IotRedisStreamRuleAction action = new IotRedisStreamRuleAction(); + IotRedisRuleAction action = new IotRedisRuleAction(); - // 2. 创建配置 - IotDataSinkRedisStreamConfig config = new IotDataSinkRedisStreamConfig() - .setHost("127.0.0.1") - .setPort(6379) - .setDatabase(0) - .setPassword("123456") - .setTopic("test-stream"); + // 2. 创建配置 - 测试 Stream 数据结构 + IotDataSinkRedisConfig config = new IotDataSinkRedisConfig(); + config.setHost("127.0.0.1"); + config.setPort(6379); + config.setDatabase(0); + config.setPassword("123456"); + config.setTopic("test-stream"); + config.setDataStructure(1); // Stream 类型 // 3. 执行测试并验证缓存 - executeAndVerifyCache(action, config, "RedisStream"); + executeAndVerifyCache(action, config, "Redis"); } @Test