Commit f88d30f1039757870225e687a3b8a434acb6238c

Authored by 芋道源码
Committed by Gitee
2 parents 50ac2ca5 a328dcf1

!1403 perf:【IoT 物联网】场景规则匹配优化

Merge pull request !1403 from puhui999/feature/iot
Showing 43 changed files with 2209 additions and 966 deletions
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 {
132 132 List<IotDeviceDO> list = deviceService.getDeviceListByCondition(deviceType, productId);
133 133 return success(convertList(list, device -> // 只返回 id、name、productId 字段
134 134 new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName())
135   - .setProductId(device.getProductId())));
  135 + .setProductId(device.getProductId()).setState(device.getState())));
136 136 }
137 137  
138 138 @PostMapping("/import")
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotRuleSceneController.java renamed to 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;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7   -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleScenePageReqVO;
8   -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneRespVO;
9   -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneSaveReqVO;
10   -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneUpdateStatusReqVO;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
  8 +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleRespVO;
  9 +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
  10 +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleUpdateStatusReqVO;
11 11 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
12   -import cn.iocoder.yudao.module.iot.service.rule.scene.IotRuleSceneService;
  12 +import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
13 13 import io.swagger.v3.oas.annotations.Operation;
14 14 import io.swagger.v3.oas.annotations.Parameter;
15 15 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -24,71 +24,70 @@ import java.util.List;
24 24 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
25 25 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
26 26  
27   -// TODO @puhui999:SceneRule 方法名,类名等;
28 27 @Tag(name = "管理后台 - IoT 场景联动")
29 28 @RestController
30   -@RequestMapping("/iot/rule-scene")
  29 +@RequestMapping("/iot/scene-rule")
31 30 @Validated
32   -public class IotRuleSceneController {
  31 +public class IotSceneRuleController {
33 32  
34 33 @Resource
35   - private IotRuleSceneService ruleSceneService;
  34 + private IotSceneRuleService sceneRuleService;
36 35  
37 36 @PostMapping("/create")
38 37 @Operation(summary = "创建场景联动")
39   - @PreAuthorize("@ss.hasPermission('iot:rule-scene:create')")
40   - public CommonResult<Long> createRuleScene(@Valid @RequestBody IotRuleSceneSaveReqVO createReqVO) {
41   - return success(ruleSceneService.createRuleScene(createReqVO));
  38 + @PreAuthorize("@ss.hasPermission('iot:scene-rule:create')")
  39 + public CommonResult<Long> createSceneRule(@Valid @RequestBody IotSceneRuleSaveReqVO createReqVO) {
  40 + return success(sceneRuleService.createSceneRule(createReqVO));
42 41 }
43 42  
44 43 @PutMapping("/update")
45 44 @Operation(summary = "更新场景联动")
46   - @PreAuthorize("@ss.hasPermission('iot:rule-scene:update')")
47   - public CommonResult<Boolean> updateRuleScene(@Valid @RequestBody IotRuleSceneSaveReqVO updateReqVO) {
48   - ruleSceneService.updateRuleScene(updateReqVO);
  45 + @PreAuthorize("@ss.hasPermission('iot:scene-rule:update')")
  46 + public CommonResult<Boolean> updateSceneRule(@Valid @RequestBody IotSceneRuleSaveReqVO updateReqVO) {
  47 + sceneRuleService.updateSceneRule(updateReqVO);
49 48 return success(true);
50 49 }
51 50  
52 51 @PutMapping("/update-status")
53 52 @Operation(summary = "更新场景联动状态")
54   - @PreAuthorize("@ss.hasPermission('iot:rule-scene:update')")
55   - public CommonResult<Boolean> updateRuleSceneStatus(@Valid @RequestBody IotRuleSceneUpdateStatusReqVO updateReqVO) {
56   - ruleSceneService.updateRuleSceneStatus(updateReqVO.getId(), updateReqVO.getStatus());
  53 + @PreAuthorize("@ss.hasPermission('iot:scene-rule:update')")
  54 + public CommonResult<Boolean> updateSceneRuleStatus(@Valid @RequestBody IotSceneRuleUpdateStatusReqVO updateReqVO) {
  55 + sceneRuleService.updateSceneRuleStatus(updateReqVO.getId(), updateReqVO.getStatus());
57 56 return success(true);
58 57 }
59 58  
60 59 @DeleteMapping("/delete")
61 60 @Operation(summary = "删除场景联动")
62 61 @Parameter(name = "id", description = "编号", required = true)
63   - @PreAuthorize("@ss.hasPermission('iot:rule-scene:delete')")
64   - public CommonResult<Boolean> deleteRuleScene(@RequestParam("id") Long id) {
65   - ruleSceneService.deleteRuleScene(id);
  62 + @PreAuthorize("@ss.hasPermission('iot:scene-rule:delete')")
  63 + public CommonResult<Boolean> deleteSceneRule(@RequestParam("id") Long id) {
  64 + sceneRuleService.deleteSceneRule(id);
66 65 return success(true);
67 66 }
68 67  
69 68 @GetMapping("/get")
70 69 @Operation(summary = "获得场景联动")
71 70 @Parameter(name = "id", description = "编号", required = true, example = "1024")
72   - @PreAuthorize("@ss.hasPermission('iot:rule-scene:query')")
73   - public CommonResult<IotRuleSceneRespVO> getRuleScene(@RequestParam("id") Long id) {
74   - IotSceneRuleDO ruleScene = ruleSceneService.getRuleScene(id);
75   - return success(BeanUtils.toBean(ruleScene, IotRuleSceneRespVO.class));
  71 + @PreAuthorize("@ss.hasPermission('iot:scene-rule:query')")
  72 + public CommonResult<IotSceneRuleRespVO> getSceneRule(@RequestParam("id") Long id) {
  73 + IotSceneRuleDO sceneRule = sceneRuleService.getSceneRule(id);
  74 + return success(BeanUtils.toBean(sceneRule, IotSceneRuleRespVO.class));
76 75 }
77 76  
78 77 @GetMapping("/page")
79 78 @Operation(summary = "获得场景联动分页")
80   - @PreAuthorize("@ss.hasPermission('iot:rule-scene:query')")
81   - public CommonResult<PageResult<IotRuleSceneRespVO>> getRuleScenePage(@Valid IotRuleScenePageReqVO pageReqVO) {
82   - PageResult<IotSceneRuleDO> pageResult = ruleSceneService.getRuleScenePage(pageReqVO);
83   - return success(BeanUtils.toBean(pageResult, IotRuleSceneRespVO.class));
  79 + @PreAuthorize("@ss.hasPermission('iot:scene-rule:query')")
  80 + public CommonResult<PageResult<IotSceneRuleRespVO>> getSceneRulePage(@Valid IotSceneRulePageReqVO pageReqVO) {
  81 + PageResult<IotSceneRuleDO> pageResult = sceneRuleService.getSceneRulePage(pageReqVO);
  82 + return success(BeanUtils.toBean(pageResult, IotSceneRuleRespVO.class));
84 83 }
85 84  
86 85 @GetMapping("/simple-list")
87 86 @Operation(summary = "获取场景联动的精简信息列表", description = "主要用于前端的下拉选项")
88   - public CommonResult<List<IotRuleSceneRespVO>> getRuleSceneSimpleList() {
89   - List<IotSceneRuleDO> list = ruleSceneService.getRuleSceneListByStatus(CommonStatusEnum.ENABLE.getStatus());
  87 + public CommonResult<List<IotSceneRuleRespVO>> getSceneRuleSimpleList() {
  88 + List<IotSceneRuleDO> list = sceneRuleService.getSceneRuleListByStatus(CommonStatusEnum.ENABLE.getStatus());
90 89 return success(convertList(list, scene -> // 只返回 id、name 字段
91   - new IotRuleSceneRespVO().setId(scene.getId()).setName(scene.getName())));
  90 + new IotSceneRuleRespVO().setId(scene.getId()).setName(scene.getName())));
92 91 }
93 92  
94 93 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleScenePageReqVO.java renamed to 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_
17 17 @Data
18 18 @EqualsAndHashCode(callSuper = true)
19 19 @ToString(callSuper = true)
20   -public class IotRuleScenePageReqVO extends PageParam {
  20 +public class IotSceneRulePageReqVO extends PageParam {
21 21  
22 22 @Schema(description = "场景名称", example = "赵六")
23 23 private String name;
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleSceneRespVO.java renamed to 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;
9 9  
10 10 @Schema(description = "管理后台 - IoT 场景联动 Response VO")
11 11 @Data
12   -public class IotRuleSceneRespVO {
  12 +public class IotSceneRuleRespVO {
13 13  
14 14 @Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865")
15 15 private Long id;
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleSceneSaveReqVO.java renamed to 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;
12 12  
13 13 @Schema(description = "管理后台 - IoT 场景联动新增/修改 Request VO")
14 14 @Data
15   -public class IotRuleSceneSaveReqVO {
  15 +public class IotSceneRuleSaveReqVO {
16 16  
17 17 @Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865")
18 18 private Long id;
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotRuleSceneUpdateStatusReqVO.java renamed to 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;
8 8  
9 9 @Schema(description = "管理后台 - IoT 场景联动更新状态 Request VO")
10 10 @Data
11   -public class IotRuleSceneUpdateStatusReqVO {
  11 +public class IotSceneRuleUpdateStatusReqVO {
12 12  
13 13 @Schema(description = "场景联动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
14 14 @NotNull(message = "场景联动编号不能为空")
... ...
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;
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
8 8 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
9 9 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
10   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
11   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneConditionOperatorEnum;
12   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneConditionTypeEnum;
13   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum;
  10 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
  11 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
  12 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
  13 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
14 14 import com.baomidou.mybatisplus.annotation.KeySequence;
15 15 import com.baomidou.mybatisplus.annotation.TableField;
16 16 import com.baomidou.mybatisplus.annotation.TableId;
... ... @@ -79,13 +79,13 @@ public class IotSceneRuleDO extends TenantBaseDO {
79 79 /**
80 80 * 场景事件类型
81 81 *
82   - * 枚举 {@link IotRuleSceneTriggerTypeEnum}
83   - * 1. {@link IotRuleSceneTriggerTypeEnum#DEVICE_STATE_UPDATE} 时,operator 非空,并且 value 为在线状态
84   - * 2. {@link IotRuleSceneTriggerTypeEnum#DEVICE_PROPERTY_POST}
85   - * {@link IotRuleSceneTriggerTypeEnum#DEVICE_EVENT_POST} 时,identifier、operator 非空,并且 value 为属性值
86   - * 3. {@link IotRuleSceneTriggerTypeEnum#DEVICE_EVENT_POST}
87   - * {@link IotRuleSceneTriggerTypeEnum#DEVICE_SERVICE_INVOKE} 时,identifier 非空,但是 operator、value 为空
88   - * 4. {@link IotRuleSceneTriggerTypeEnum#TIMER} 时,conditions 非空,并且设备无关(无需 productId、deviceId 字段)
  82 + * 枚举 {@link IotSceneRuleTriggerTypeEnum}
  83 + * 1. {@link IotSceneRuleTriggerTypeEnum#DEVICE_STATE_UPDATE} 时,operator 非空,并且 value 为在线状态
  84 + * 2. {@link IotSceneRuleTriggerTypeEnum#DEVICE_PROPERTY_POST}
  85 + * {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST} 时,identifier、operator 非空,并且 value 为属性值
  86 + * 3. {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST}
  87 + * {@link IotSceneRuleTriggerTypeEnum#DEVICE_SERVICE_INVOKE} 时,identifier 非空,但是 operator、value 为空
  88 + * 4. {@link IotSceneRuleTriggerTypeEnum#TIMER} 时,conditions 非空,并且设备无关(无需 productId、deviceId 字段)
89 89 */
90 90 private Integer type;
91 91  
... ... @@ -111,14 +111,14 @@ public class IotSceneRuleDO extends TenantBaseDO {
111 111 /**
112 112 * 操作符
113 113 *
114   - * 枚举 {@link IotRuleSceneConditionOperatorEnum}
  114 + * 枚举 {@link IotSceneRuleConditionOperatorEnum}
115 115 */
116 116 private String operator;
117 117 /**
118 118 * 参数(属性值、在线状态)
119 119 * <p>
120 120 * 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。
121   - * 例如说,{@link IotRuleSceneConditionOperatorEnum#IN}、{@link IotRuleSceneConditionOperatorEnum#BETWEEN}
  121 + * 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN}
122 122 */
123 123 private String value;
124 124  
... ... @@ -148,10 +148,10 @@ public class IotSceneRuleDO extends TenantBaseDO {
148 148 /**
149 149 * 触发条件类型
150 150 *
151   - * 枚举 {@link IotRuleSceneConditionTypeEnum}
152   - * 1. {@link IotRuleSceneConditionTypeEnum#DEVICE_STATE} 时,operator 非空,并且 value 为在线状态
153   - * 2. {@link IotRuleSceneConditionTypeEnum#DEVICE_PROPERTY} 时,identifier、operator 非空,并且 value 为属性值
154   - * 3. {@link IotRuleSceneConditionTypeEnum#CURRENT_TIME} 时,operator 非空(使用 DATE_TIME_ 和 TIME_ 部分),并且 value 非空
  151 + * 枚举 {@link IotSceneRuleConditionTypeEnum}
  152 + * 1. {@link IotSceneRuleConditionTypeEnum#DEVICE_STATE} 时,operator 非空,并且 value 为在线状态
  153 + * 2. {@link IotSceneRuleConditionTypeEnum#DEVICE_PROPERTY} 时,identifier、operator 非空,并且 value 为属性值
  154 + * 3. {@link IotSceneRuleConditionTypeEnum#CURRENT_TIME} 时,operator 非空(使用 DATE_TIME_ 和 TIME_ 部分),并且 value 非空
155 155 */
156 156 private Integer type;
157 157  
... ... @@ -176,14 +176,14 @@ public class IotSceneRuleDO extends TenantBaseDO {
176 176 /**
177 177 * 操作符
178 178 *
179   - * 枚举 {@link IotRuleSceneConditionOperatorEnum}
  179 + * 枚举 {@link IotSceneRuleConditionOperatorEnum}
180 180 */
181 181 private String operator;
182 182 /**
183 183 * 参数
184 184 *
185 185 * 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。
186   - * 例如说,{@link IotRuleSceneConditionOperatorEnum#IN}、{@link IotRuleSceneConditionOperatorEnum#BETWEEN}
  186 + * 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN}
187 187 */
188 188 private String param;
189 189  
... ... @@ -198,11 +198,11 @@ public class IotSceneRuleDO extends TenantBaseDO {
198 198 /**
199 199 * 执行类型
200 200 *
201   - * 枚举 {@link IotRuleSceneActionTypeEnum}
202   - * 1. {@link IotRuleSceneActionTypeEnum#DEVICE_PROPERTY_SET} 时,params 非空
203   - * {@link IotRuleSceneActionTypeEnum#DEVICE_SERVICE_INVOKE} 时,params 非空
204   - * 2. {@link IotRuleSceneActionTypeEnum#ALERT_TRIGGER} 时,alertConfigId 为空,因为是 {@link IotAlertConfigDO} 里面关联它
205   - * 3. {@link IotRuleSceneActionTypeEnum#ALERT_RECOVER} 时,alertConfigId 非空
  201 + * 枚举 {@link IotSceneRuleActionTypeEnum}
  202 + * 1. {@link IotSceneRuleActionTypeEnum#DEVICE_PROPERTY_SET} 时,params 非空
  203 + * {@link IotSceneRuleActionTypeEnum#DEVICE_SERVICE_INVOKE} 时,params 非空
  204 + * 2. {@link IotSceneRuleActionTypeEnum#ALERT_TRIGGER} 时,alertConfigId 为空,因为是 {@link IotAlertConfigDO} 里面关联它
  205 + * 3. {@link IotSceneRuleActionTypeEnum#ALERT_RECOVER} 时,alertConfigId 非空
206 206 */
207 207 private Integer type;
208 208  
... ...
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;
18 18 @JsonSubTypes({
19 19 @JsonSubTypes.Type(value = IotDataSinkHttpConfig.class, name = "1"),
20 20 @JsonSubTypes.Type(value = IotDataSinkMqttConfig.class, name = "10"),
21   - @JsonSubTypes.Type(value = IotDataSinkRedisStreamConfig.class, name = "21"),
  21 + @JsonSubTypes.Type(value = IotDataSinkRedisConfig.class, name = "21"),
22 22 @JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"),
23 23 @JsonSubTypes.Type(value = IotDataSinkRabbitMQConfig.class, name = "31"),
24 24 @JsonSubTypes.Type(value = IotDataSinkKafkaConfig.class, name = "32"),
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisStreamConfig.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java
1 1 package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
2 2  
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.iot.enums.rule.IotRedisDataStructureEnum;
3 5 import lombok.Data;
4 6  
5 7 /**
6   - * IoT Redis Stream 配置 {@link IotAbstractDataSinkConfig} 实现类
  8 + * IoT Redis 配置 {@link IotAbstractDataSinkConfig} 实现类
7 9 *
8 10 * @author HUIHUI
9 11 */
10 12 @Data
11   -public class IotDataSinkRedisStreamConfig extends IotAbstractDataSinkConfig {
  13 +public class IotDataSinkRedisConfig extends IotAbstractDataSinkConfig {
12 14  
13 15 /**
14 16 * Redis 服务器地址
... ... @@ -28,7 +30,35 @@ public class IotDataSinkRedisStreamConfig extends IotAbstractDataSinkConfig {
28 30 private Integer database;
29 31  
30 32 /**
31   - * 主题
  33 + * Redis 数据结构类型
  34 + * <p>
  35 + * 枚举 {@link IotRedisDataStructureEnum}
  36 + */
  37 + @InEnum(IotRedisDataStructureEnum.class)
  38 + private Integer dataStructure;
  39 +
  40 + /**
  41 + * 主题/键名
  42 + * <p>
  43 + * 对于不同的数据结构:
  44 + * - Stream: 流的键名
  45 + * - Hash: Hash 的键名
  46 + * - List: 列表的键名
  47 + * - Set: 集合的键名
  48 + * - ZSet: 有序集合的键名
  49 + * - String: 字符串的键名
32 50 */
33 51 private String topic;
34   -}
35 52 \ No newline at end of file
  53 +
  54 + /**
  55 + * Hash 字段名(仅当 dataStructure 为 HASH 时使用)
  56 + */
  57 + private String hashField;
  58 +
  59 + /**
  60 + * ZSet 分数字段(仅当 dataStructure 为 ZSET 时使用)
  61 + * 指定消息中哪个字段作为分数,如果不指定则使用当前时间戳
  62 + */
  63 + private String scoreField;
  64 +
  65 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotRuleSceneMapper.java renamed to 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;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6   -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleScenePageReqVO;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
8 8 import org.apache.ibatis.annotations.Mapper;
9 9  
... ... @@ -15,9 +15,9 @@ import java.util.List;
15 15 * @author HUIHUI
16 16 */
17 17 @Mapper
18   -public interface IotRuleSceneMapper extends BaseMapperX<IotSceneRuleDO> {
  18 +public interface IotSceneRuleMapper extends BaseMapperX<IotSceneRuleDO> {
19 19  
20   - default PageResult<IotSceneRuleDO> selectPage(IotRuleScenePageReqVO reqVO) {
  20 + default PageResult<IotSceneRuleDO> selectPage(IotSceneRulePageReqVO reqVO) {
21 21 return selectPage(reqVO, new LambdaQueryWrapperX<IotSceneRuleDO>()
22 22 .likeIfPresent(IotSceneRuleDO::getName, reqVO.getName())
23 23 .likeIfPresent(IotSceneRuleDO::getDescription, reqVO.getDescription())
... ...
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&lt;Integer&gt; {
22 22 MQTT(10, "MQTT"), // TODO 待实现;
23 23  
24 24 DATABASE(20, "Database"), // TODO @puhui999:待实现;可以简单点,对应的表名是什么,字段先固定了。
25   - REDIS_STREAM(21, "Redis Stream"), // TODO @puhui999:改成 Redis;然后枚举不同的数据结构?这样,枚举就可以是 Redis 了
  25 + REDIS(21, "Redis"),
26 26  
27 27 ROCKETMQ(30, "RocketMQ"),
28 28 RABBITMQ(31, "RabbitMQ"),
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRedisDataStructureEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.enums.rule;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  4 +import lombok.Getter;
  5 +import lombok.RequiredArgsConstructor;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * IoT Redis 数据结构类型枚举
  11 + *
  12 + * @author HUIHUI
  13 + */
  14 +@RequiredArgsConstructor
  15 +@Getter
  16 +public enum IotRedisDataStructureEnum implements ArrayValuable<Integer> {
  17 +
  18 + STREAM(1, "Stream"),
  19 + HASH(2, "Hash"),
  20 + LIST(3, "List"),
  21 + SET(4, "Set"),
  22 + ZSET(5, "ZSet"),
  23 + STRING(6, "String");
  24 +
  25 + private final Integer type;
  26 +
  27 + private final String name;
  28 +
  29 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRedisDataStructureEnum::getType).toArray(Integer[]::new);
  30 +
  31 + @Override
  32 + public Integer[] array() {
  33 + return ARRAYS;
  34 + }
  35 +
  36 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneActionTypeEnum.java renamed to 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;
14 14 */
15 15 @RequiredArgsConstructor
16 16 @Getter
17   -public enum IotRuleSceneActionTypeEnum implements ArrayValuable<Integer> {
  17 +public enum IotSceneRuleActionTypeEnum implements ArrayValuable<Integer> {
18 18  
19 19 /**
20 20 * 设备属性设置
... ... @@ -42,7 +42,7 @@ public enum IotRuleSceneActionTypeEnum implements ArrayValuable&lt;Integer&gt; {
42 42  
43 43 private final Integer type;
44 44  
45   - public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneActionTypeEnum::getType).toArray(Integer[]::new);
  45 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleActionTypeEnum::getType).toArray(Integer[]::new);
46 46  
47 47 @Override
48 48 public Integer[] array() {
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneConditionOperatorEnum.java renamed to 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;
14 14 */
15 15 @RequiredArgsConstructor
16 16 @Getter
17   -public enum IotRuleSceneConditionOperatorEnum implements ArrayValuable<String> {
  17 +public enum IotSceneRuleConditionOperatorEnum implements ArrayValuable<String> {
18 18  
19 19 EQUALS("=", "#source == #value"),
20 20 NOT_EQUALS("!=", "!(#source == #value)"),
... ... @@ -53,7 +53,7 @@ public enum IotRuleSceneConditionOperatorEnum implements ArrayValuable&lt;String&gt; {
53 53 private final String operator;
54 54 private final String springExpression;
55 55  
56   - public static final String[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneConditionOperatorEnum::getOperator).toArray(String[]::new);
  56 + public static final String[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleConditionOperatorEnum::getOperator).toArray(String[]::new);
57 57  
58 58 /**
59 59 * Spring 表达式 - 原始值
... ... @@ -68,7 +68,7 @@ public enum IotRuleSceneConditionOperatorEnum implements ArrayValuable&lt;String&gt; {
68 68 */
69 69 public static final String SPRING_EXPRESSION_VALUE_LIST = "values";
70 70  
71   - public static IotRuleSceneConditionOperatorEnum operatorOf(String operator) {
  71 + public static IotSceneRuleConditionOperatorEnum operatorOf(String operator) {
72 72 return ArrayUtil.firstMatch(item -> item.getOperator().equals(operator), values());
73 73 }
74 74  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneConditionTypeEnum.java renamed to 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;
13 13 */
14 14 @RequiredArgsConstructor
15 15 @Getter
16   -public enum IotRuleSceneConditionTypeEnum implements ArrayValuable<Integer> {
  16 +public enum IotSceneRuleConditionTypeEnum implements ArrayValuable<Integer> {
17 17  
18 18 DEVICE_STATE(1, "设备状态"),
19 19 DEVICE_PROPERTY(2, "设备属性"),
... ... @@ -25,7 +25,7 @@ public enum IotRuleSceneConditionTypeEnum implements ArrayValuable&lt;Integer&gt; {
25 25 private final Integer type;
26 26 private final String name;
27 27  
28   - public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneConditionTypeEnum::getType).toArray(Integer[]::new);
  28 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleConditionTypeEnum::getType).toArray(Integer[]::new);
29 29  
30 30 @Override
31 31 public Integer[] array() {
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneTriggerTypeEnum.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java
... ... @@ -16,10 +16,7 @@ import java.util.Arrays;
16 16 */
17 17 @RequiredArgsConstructor
18 18 @Getter
19   -public enum IotRuleSceneTriggerTypeEnum implements ArrayValuable<Integer> {
20   -
21   - @Deprecated
22   - DEVICE(1), // 设备触发 // TODO @puhui999:@芋艿:这个可以作废
  19 +public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable<Integer> {
23 20  
24 21 // TODO @芋艿:后续“对应”部分,要 @下,等包结构梳理完;
25 22 /**
... ... @@ -56,11 +53,25 @@ public enum IotRuleSceneTriggerTypeEnum implements ArrayValuable&lt;Integer&gt; {
56 53  
57 54 private final Integer type;
58 55  
59   - public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneTriggerTypeEnum::getType).toArray(Integer[]::new);
  56 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleTriggerTypeEnum::getType).toArray(Integer[]::new);
60 57  
61 58 @Override
62 59 public Integer[] array() {
63 60 return ARRAYS;
64 61 }
65 62  
  63 +
  64 + /**
  65 + * 根据类型值查找触发器类型枚举
  66 + *
  67 + * @param typeValue 类型值
  68 + * @return 触发器类型枚举
  69 + */
  70 + public static IotSceneRuleTriggerTypeEnum findTriggerTypeEnum(Integer typeValue) {
  71 + return Arrays.stream(IotSceneRuleTriggerTypeEnum.values())
  72 + .filter(type -> type.getType().equals(typeValue))
  73 + .findFirst()
  74 + .orElse(null);
  75 + }
  76 +
66 77 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/rule/IotRuleSceneJob.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/rule/IotSceneRuleJob.java
1 1 package cn.iocoder.yudao.module.iot.job.rule;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
4   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum;
5   -import cn.iocoder.yudao.module.iot.service.rule.scene.IotRuleSceneService;
  4 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  5 +import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
6 6 import jakarta.annotation.Resource;
7 7 import lombok.extern.slf4j.Slf4j;
8 8 import org.quartz.JobExecutionContext;
... ... @@ -11,48 +11,48 @@ import org.springframework.scheduling.quartz.QuartzJobBean;
11 11 import java.util.Map;
12 12  
13 13 /**
14   - * IoT 规则场景 Job,用于执行 {@link IotRuleSceneTriggerTypeEnum#TIMER} 类型的规则场景
  14 + * IoT 规则场景 Job,用于执行 {@link IotSceneRuleTriggerTypeEnum#TIMER} 类型的规则场景
15 15 *
16 16 * @author 芋道源码
17 17 */
18 18 @Slf4j
19   -public class IotRuleSceneJob extends QuartzJobBean {
  19 +public class IotSceneRuleJob extends QuartzJobBean {
20 20  
21 21 /**
22 22 * JobData Key - 规则场景编号
23 23 */
24   - public static final String JOB_DATA_KEY_RULE_SCENE_ID = "ruleSceneId";
  24 + public static final String JOB_DATA_KEY_RULE_SCENE_ID = "sceneRuleId";
25 25  
26 26 @Resource
27   - private IotRuleSceneService ruleSceneService;
  27 + private IotSceneRuleService sceneRuleService;
28 28  
29 29 @Override
30 30 protected void executeInternal(JobExecutionContext context) {
31 31 // 获得规则场景编号
32   - Long ruleSceneId = context.getMergedJobDataMap().getLong(JOB_DATA_KEY_RULE_SCENE_ID);
  32 + Long sceneRuleId = context.getMergedJobDataMap().getLong(JOB_DATA_KEY_RULE_SCENE_ID);
33 33  
34 34 // 执行规则场景
35   - ruleSceneService.executeRuleSceneByTimer(ruleSceneId);
  35 + sceneRuleService.executeSceneRuleByTimer(sceneRuleId);
36 36 }
37 37  
38 38 /**
39 39 * 创建 JobData Map
40 40 *
41   - * @param ruleSceneId 规则场景编号
  41 + * @param sceneRuleId 规则场景编号
42 42 * @return JobData Map
43 43 */
44   - public static Map<String, Object> buildJobDataMap(Long ruleSceneId) {
45   - return MapUtil.of(JOB_DATA_KEY_RULE_SCENE_ID, ruleSceneId);
  44 + public static Map<String, Object> buildJobDataMap(Long sceneRuleId) {
  45 + return MapUtil.of(JOB_DATA_KEY_RULE_SCENE_ID, sceneRuleId);
46 46 }
47 47  
48 48 /**
49 49 * 创建 Job 名字
50 50 *
51   - * @param ruleSceneId 规则场景编号
  51 + * @param sceneRuleId 规则场景编号
52 52 * @return Job 名字
53 53 */
54   - public static String buildJobName(Long ruleSceneId) {
55   - return String.format("%s_%d", IotRuleSceneJob.class.getSimpleName(), ruleSceneId);
  54 + public static String buildJobName(Long sceneRuleId) {
  55 + return String.format("%s_%d", IotSceneRuleJob.class.getSimpleName(), sceneRuleId);
56 56 }
57 57  
58 58 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotRuleSceneMessageHandler.java renamed to 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;
3 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 4 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
5 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6   -import cn.iocoder.yudao.module.iot.service.rule.scene.IotRuleSceneService;
  6 +import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
7 7 import jakarta.annotation.PostConstruct;
8 8 import jakarta.annotation.Resource;
9 9 import lombok.extern.slf4j.Slf4j;
... ... @@ -17,10 +17,10 @@ import org.springframework.stereotype.Component;
17 17 */
18 18 @Component
19 19 @Slf4j
20   -public class IotRuleSceneMessageHandler implements IotMessageSubscriber<IotDeviceMessage> {
  20 +public class IotSceneRuleMessageHandler implements IotMessageSubscriber<IotDeviceMessage> {
21 21  
22 22 @Resource
23   - private IotRuleSceneService ruleSceneService;
  23 + private IotSceneRuleService sceneRuleService;
24 24  
25 25 @Resource
26 26 private IotMessageBus messageBus;
... ... @@ -46,7 +46,7 @@ public class IotRuleSceneMessageHandler implements IotMessageSubscriber&lt;IotDevic
46 46 return;
47 47 }
48 48 log.info("[onMessage][消息内容({})]", message);
49   - ruleSceneService.executeRuleSceneByDevice(message);
  49 + sceneRuleService.executeSceneRuleByDevice(message);
50 50 }
51 51  
52 52 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigServiceImpl.java
... ... @@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConf
6 6 import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigSaveReqVO;
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
8 8 import cn.iocoder.yudao.module.iot.dal.mysql.alert.IotAlertConfigMapper;
9   -import cn.iocoder.yudao.module.iot.service.rule.scene.IotRuleSceneService;
  9 +import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
10 10 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
11 11 import jakarta.annotation.Resource;
12 12 import org.springframework.context.annotation.Lazy;
... ... @@ -32,7 +32,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService {
32 32  
33 33 @Resource
34 34 @Lazy // 延迟,避免循环依赖报错
35   - private IotRuleSceneService ruleSceneService;
  35 + private IotSceneRuleService sceneRuleService;
36 36  
37 37 @Resource
38 38 private AdminUserApi adminUserApi;
... ... @@ -40,7 +40,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService {
40 40 @Override
41 41 public Long createAlertConfig(IotAlertConfigSaveReqVO createReqVO) {
42 42 // 校验关联数据是否存在
43   - ruleSceneService.validateRuleSceneList(createReqVO.getSceneRuleIds());
  43 + sceneRuleService.validateSceneRuleList(createReqVO.getSceneRuleIds());
44 44 adminUserApi.validateUserList(createReqVO.getReceiveUserIds());
45 45  
46 46 // 插入
... ... @@ -54,7 +54,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService {
54 54 // 校验存在
55 55 validateAlertConfigExists(updateReqVO.getId());
56 56 // 校验关联数据是否存在
57   - ruleSceneService.validateRuleSceneList(updateReqVO.getSceneRuleIds());
  57 + sceneRuleService.validateSceneRuleList(updateReqVO.getSceneRuleIds());
58 58 adminUserApi.validateUserList(updateReqVO.getReceiveUserIds());
59 59  
60 60 // 更新
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisRuleAction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.data.action;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  5 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkRedisConfig;
  7 +import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotRedisDataStructureEnum;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.redisson.Redisson;
  11 +import org.redisson.api.RedissonClient;
  12 +import org.redisson.config.Config;
  13 +import org.redisson.config.SingleServerConfig;
  14 +import org.redisson.spring.data.connection.RedissonConnectionFactory;
  15 +import org.springframework.data.redis.connection.RedisConnectionFactory;
  16 +import org.springframework.data.redis.connection.stream.ObjectRecord;
  17 +import org.springframework.data.redis.connection.stream.StreamRecords;
  18 +import org.springframework.data.redis.core.RedisTemplate;
  19 +import org.springframework.data.redis.serializer.RedisSerializer;
  20 +import org.springframework.stereotype.Component;
  21 +
  22 +import java.util.Map;
  23 +
  24 +/**
  25 + * Redis 的 {@link IotDataRuleAction} 实现类
  26 + * 支持多种 Redis 数据结构:Stream、Hash、List、Set、ZSet、String
  27 + *
  28 + * @author HUIHUI
  29 + */
  30 +@Component
  31 +@Slf4j
  32 +public class IotRedisRuleAction extends
  33 + IotDataRuleCacheableAction<IotDataSinkRedisConfig, RedisTemplate<String, Object>> {
  34 +
  35 + @Override
  36 + public Integer getType() {
  37 + return IotDataSinkTypeEnum.REDIS.getType();
  38 + }
  39 +
  40 + @Override
  41 + public void execute(IotDeviceMessage message, IotDataSinkRedisConfig config) throws Exception {
  42 + // 1. 获取 RedisTemplate
  43 + RedisTemplate<String, Object> redisTemplate = getProducer(config);
  44 +
  45 + // 2. 根据数据结构类型执行不同的操作
  46 + String messageJson = JsonUtils.toJsonString(message);
  47 + IotRedisDataStructureEnum dataStructure = getDataStructureByType(config.getDataStructure());
  48 +
  49 + switch (dataStructure) {
  50 + case STREAM:
  51 + executeStream(redisTemplate, config, messageJson);
  52 + break;
  53 + case HASH:
  54 + executeHash(redisTemplate, config, message, messageJson);
  55 + break;
  56 + case LIST:
  57 + executeList(redisTemplate, config, messageJson);
  58 + break;
  59 + case SET:
  60 + executeSet(redisTemplate, config, messageJson);
  61 + break;
  62 + case ZSET:
  63 + executeZSet(redisTemplate, config, message, messageJson);
  64 + break;
  65 + case STRING:
  66 + executeString(redisTemplate, config, messageJson);
  67 + break;
  68 + default:
  69 + throw new IllegalArgumentException("不支持的 Redis 数据结构类型: " + dataStructure);
  70 + }
  71 +
  72 + log.info("[execute][消息发送成功] dataStructure: {}, config: {}", dataStructure.getName(), config);
  73 + }
  74 +
  75 + /**
  76 + * 执行 Stream 操作
  77 + */
  78 + private void executeStream(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config, String messageJson) {
  79 + ObjectRecord<String, ?> record = StreamRecords.newRecord()
  80 + .ofObject(messageJson).withStreamKey(config.getTopic());
  81 + redisTemplate.opsForStream().add(record);
  82 + }
  83 +
  84 + /**
  85 + * 执行 Hash 操作
  86 + */
  87 + private void executeHash(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config,
  88 + IotDeviceMessage message, String messageJson) {
  89 + String hashField = StrUtil.isNotBlank(config.getHashField()) ?
  90 + config.getHashField() : String.valueOf(message.getDeviceId());
  91 + redisTemplate.opsForHash().put(config.getTopic(), hashField, messageJson);
  92 + }
  93 +
  94 + /**
  95 + * 执行 List 操作
  96 + */
  97 + private void executeList(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config, String messageJson) {
  98 + redisTemplate.opsForList().rightPush(config.getTopic(), messageJson);
  99 + }
  100 +
  101 + /**
  102 + * 执行 Set 操作
  103 + */
  104 + private void executeSet(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config, String messageJson) {
  105 + redisTemplate.opsForSet().add(config.getTopic(), messageJson);
  106 + }
  107 +
  108 + /**
  109 + * 执行 ZSet 操作
  110 + */
  111 + private void executeZSet(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config,
  112 + IotDeviceMessage message, String messageJson) {
  113 + double score;
  114 + if (StrUtil.isNotBlank(config.getScoreField())) {
  115 + // 尝试从消息中获取分数字段
  116 + try {
  117 + Map<String, Object> messageMap = JsonUtils.parseObject(messageJson, Map.class);
  118 + Object scoreValue = messageMap.get(config.getScoreField());
  119 + score = scoreValue instanceof Number ? ((Number) scoreValue).doubleValue() : System.currentTimeMillis();
  120 + } catch (Exception e) {
  121 + score = System.currentTimeMillis();
  122 + }
  123 + } else {
  124 + // 使用当前时间戳作为分数
  125 + score = System.currentTimeMillis();
  126 + }
  127 + redisTemplate.opsForZSet().add(config.getTopic(), messageJson, score);
  128 + }
  129 +
  130 + /**
  131 + * 执行 String 操作
  132 + */
  133 + private void executeString(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config, String messageJson) {
  134 + redisTemplate.opsForValue().set(config.getTopic(), messageJson);
  135 + }
  136 +
  137 + @Override
  138 + protected RedisTemplate<String, Object> initProducer(IotDataSinkRedisConfig config) {
  139 + // 1.1 创建 Redisson 配置
  140 + Config redissonConfig = new Config();
  141 + SingleServerConfig serverConfig = redissonConfig.useSingleServer()
  142 + .setAddress("redis://" + config.getHost() + ":" + config.getPort())
  143 + .setDatabase(config.getDatabase());
  144 + // 1.2 设置密码(如果有)
  145 + if (StrUtil.isNotBlank(config.getPassword())) {
  146 + serverConfig.setPassword(config.getPassword());
  147 + }
  148 +
  149 + // 2.1 创建 RedisTemplate 并配置
  150 + RedissonClient redisson = Redisson.create(redissonConfig);
  151 + RedisTemplate<String, Object> template = new RedisTemplate<>();
  152 + template.setConnectionFactory(new RedissonConnectionFactory(redisson));
  153 + // 2.2 设置序列化器
  154 + template.setKeySerializer(RedisSerializer.string());
  155 + template.setHashKeySerializer(RedisSerializer.string());
  156 + template.setValueSerializer(RedisSerializer.json());
  157 + template.setHashValueSerializer(RedisSerializer.json());
  158 + template.afterPropertiesSet();
  159 + return template;
  160 + }
  161 +
  162 + @Override
  163 + protected void closeProducer(RedisTemplate<String, Object> producer) throws Exception {
  164 + RedisConnectionFactory factory = producer.getConnectionFactory();
  165 + if (factory != null) {
  166 + ((RedissonConnectionFactory) factory).destroy();
  167 + }
  168 + }
  169 +
  170 + /**
  171 + * 根据类型值获取数据结构枚举
  172 + */
  173 + private IotRedisDataStructureEnum getDataStructureByType(Integer type) {
  174 + for (IotRedisDataStructureEnum dataStructure : IotRedisDataStructureEnum.values()) {
  175 + if (dataStructure.getType().equals(type)) {
  176 + return dataStructure;
  177 + }
  178 + }
  179 + throw new IllegalArgumentException("不支持的 Redis 数据结构类型: " + type);
  180 + }
  181 +
  182 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisStreamRuleAction.java deleted
1   -package cn.iocoder.yudao.module.iot.service.rule.data.action;
2   -
3   -import cn.hutool.core.util.StrUtil;
4   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5   -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkRedisStreamConfig;
6   -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
7   -import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
8   -import lombok.extern.slf4j.Slf4j;
9   -import org.redisson.Redisson;
10   -import org.redisson.api.RedissonClient;
11   -import org.redisson.config.Config;
12   -import org.redisson.config.SingleServerConfig;
13   -import org.redisson.spring.data.connection.RedissonConnectionFactory;
14   -import org.springframework.data.redis.connection.RedisConnectionFactory;
15   -import org.springframework.data.redis.connection.stream.ObjectRecord;
16   -import org.springframework.data.redis.connection.stream.StreamRecords;
17   -import org.springframework.data.redis.core.RedisTemplate;
18   -import org.springframework.data.redis.serializer.RedisSerializer;
19   -import org.springframework.stereotype.Component;
20   -
21   -/**
22   - * Redis Stream 的 {@link IotDataRuleAction} 实现类
23   - *
24   - * @author HUIHUI
25   - */
26   -@Component
27   -@Slf4j
28   -public class IotRedisStreamRuleAction extends
29   - IotDataRuleCacheableAction<IotDataSinkRedisStreamConfig, RedisTemplate<String, Object>> {
30   -
31   - @Override
32   - public Integer getType() {
33   - return IotDataSinkTypeEnum.REDIS_STREAM.getType();
34   - }
35   -
36   - @Override
37   - public void execute(IotDeviceMessage message, IotDataSinkRedisStreamConfig config) throws Exception {
38   - // 1. 获取 RedisTemplate
39   - RedisTemplate<String, Object> redisTemplate = getProducer(config);
40   -
41   - // 2. 创建并发送 Stream 记录
42   - ObjectRecord<String, ?> record = StreamRecords.newRecord()
43   - .ofObject(JsonUtils.toJsonString(message)).withStreamKey(config.getTopic());
44   - String recordId = String.valueOf(redisTemplate.opsForStream().add(record));
45   - log.info("[execute][消息发送成功] messageId: {}, config: {}", recordId, config);
46   - }
47   -
48   - @Override
49   - protected RedisTemplate<String, Object> initProducer(IotDataSinkRedisStreamConfig config) {
50   - // 1.1 创建 Redisson 配置
51   - Config redissonConfig = new Config();
52   - SingleServerConfig serverConfig = redissonConfig.useSingleServer()
53   - .setAddress("redis://" + config.getHost() + ":" + config.getPort())
54   - .setDatabase(config.getDatabase());
55   - // 1.2 设置密码(如果有)
56   - if (StrUtil.isNotBlank(config.getPassword())) {
57   - serverConfig.setPassword(config.getPassword());
58   - }
59   -
60   - // 2.1 创建 RedisTemplate 并配置
61   - RedissonClient redisson = Redisson.create(redissonConfig);
62   - RedisTemplate<String, Object> template = new RedisTemplate<>();
63   - template.setConnectionFactory(new RedissonConnectionFactory(redisson));
64   - // 2.2 设置序列化器
65   - template.setKeySerializer(RedisSerializer.string());
66   - template.setHashKeySerializer(RedisSerializer.string());
67   - template.setValueSerializer(RedisSerializer.json());
68   - template.setHashValueSerializer(RedisSerializer.json());
69   - template.afterPropertiesSet();
70   - return template;
71   - }
72   -
73   - @Override
74   - protected void closeProducer(RedisTemplate<String, Object> producer) throws Exception {
75   - RedisConnectionFactory factory = producer.getConnectionFactory();
76   - if (factory != null) {
77   - ((RedissonConnectionFactory) factory).destroy();
78   - }
79   - }
80   -
81   -}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotRuleSceneService.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleService.java
1 1 package cn.iocoder.yudao.module.iot.service.rule.scene;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleScenePageReqVO;
5   -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneSaveReqVO;
  4 +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
  5 +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
6 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
8   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
9 9 import jakarta.validation.Valid;
10 10  
11 11 import java.util.Collection;
... ... @@ -16,7 +16,7 @@ import java.util.List;
16 16 *
17 17 * @author 芋道源码
18 18 */
19   -public interface IotRuleSceneService {
  19 +public interface IotSceneRuleService {
20 20  
21 21 /**
22 22 * 创建场景联动
... ... @@ -24,14 +24,14 @@ public interface IotRuleSceneService {
24 24 * @param createReqVO 创建信息
25 25 * @return 编号
26 26 */
27   - Long createRuleScene(@Valid IotRuleSceneSaveReqVO createReqVO);
  27 + Long createSceneRule(@Valid IotSceneRuleSaveReqVO createReqVO);
28 28  
29 29 /**
30 30 * 更新场景联动
31 31 *
32 32 * @param updateReqVO 更新信息
33 33 */
34   - void updateRuleScene(@Valid IotRuleSceneSaveReqVO updateReqVO);
  34 + void updateSceneRule(@Valid IotSceneRuleSaveReqVO updateReqVO);
35 35  
36 36 /**
37 37 * 更新场景联动状态
... ... @@ -39,14 +39,14 @@ public interface IotRuleSceneService {
39 39 * @param id 场景联动编号
40 40 * @param status 状态
41 41 */
42   - void updateRuleSceneStatus(Long id, Integer status);
  42 + void updateSceneRuleStatus(Long id, Integer status);
43 43  
44 44 /**
45 45 * 删除场景联动
46 46 *
47 47 * @param id 编号
48 48 */
49   - void deleteRuleScene(Long id);
  49 + void deleteSceneRule(Long id);
50 50  
51 51 /**
52 52 * 获得场景联动
... ... @@ -54,7 +54,7 @@ public interface IotRuleSceneService {
54 54 * @param id 编号
55 55 * @return 场景联动
56 56 */
57   - IotSceneRuleDO getRuleScene(Long id);
  57 + IotSceneRuleDO getSceneRule(Long id);
58 58  
59 59 /**
60 60 * 获得场景联动分页
... ... @@ -62,7 +62,7 @@ public interface IotRuleSceneService {
62 62 * @param pageReqVO 分页查询
63 63 * @return 场景联动分页
64 64 */
65   - PageResult<IotSceneRuleDO> getRuleScenePage(IotRuleScenePageReqVO pageReqVO);
  65 + PageResult<IotSceneRuleDO> getSceneRulePage(IotSceneRulePageReqVO pageReqVO);
66 66  
67 67 /**
68 68 * 校验规则场景联动规则编号们是否存在。如下情况,视为无效:
... ... @@ -70,7 +70,7 @@ public interface IotRuleSceneService {
70 70 *
71 71 * @param ids 场景联动规则编号数组
72 72 */
73   - void validateRuleSceneList(Collection<Long> ids);
  73 + void validateSceneRuleList(Collection<Long> ids);
74 74  
75 75 /**
76 76 * 获得指定状态的场景联动列表
... ... @@ -78,29 +78,33 @@ public interface IotRuleSceneService {
78 78 * @param status 状态
79 79 * @return 场景联动列表
80 80 */
81   - List<IotSceneRuleDO> getRuleSceneListByStatus(Integer status);
  81 + List<IotSceneRuleDO> getSceneRuleListByStatus(Integer status);
82 82  
83 83 /**
84 84 * 【缓存】获得指定设备的场景列表
85 85 *
86   - * @param productKey 产品 Key
87   - * @param deviceName 设备名称
  86 + * @param productId 产品 ID
  87 + * @param deviceId 设备 ID
88 88 * @return 场景列表
89 89 */
90   - List<IotSceneRuleDO> getRuleSceneListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName);
  90 + List<IotSceneRuleDO> getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId);
91 91  
92 92 /**
93   - * 基于 {@link IotRuleSceneTriggerTypeEnum#DEVICE} 场景,执行规则场景
94   - *
  93 + * 基于 {@link IotSceneRuleTriggerTypeEnum} 场景,执行规则场景
  94 + * 1. {@link IotSceneRuleTriggerTypeEnum#DEVICE_STATE_UPDATE}
  95 + * 2. {@link IotSceneRuleTriggerTypeEnum#DEVICE_PROPERTY_POST}
  96 + * {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST}
  97 + * 3. {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST}
  98 + * {@link IotSceneRuleTriggerTypeEnum#DEVICE_SERVICE_INVOKE}
95 99 * @param message 消息
96 100 */
97   - void executeRuleSceneByDevice(IotDeviceMessage message);
  101 + void executeSceneRuleByDevice(IotDeviceMessage message);
98 102  
99 103 /**
100   - * 基于 {@link IotRuleSceneTriggerTypeEnum#TIMER} 场景,执行规则场景
  104 + * 基于 {@link IotSceneRuleTriggerTypeEnum#TIMER} 场景,执行规则场景
101 105 *
102 106 * @param id 场景联动规则编号
103 107 */
104   - void executeRuleSceneByTimer(Long id);
  108 + void executeSceneRuleByTimer(Long id);
105 109  
106 110 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotRuleSceneServiceImpl.java renamed to 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;
7 7 import cn.hutool.core.util.NumberUtil;
8 8 import cn.hutool.core.util.ObjUtil;
9 9 import cn.hutool.core.util.StrUtil;
  10 +import cn.hutool.extra.spring.SpringUtil;
10 11 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
11 12 import cn.iocoder.yudao.framework.common.pojo.PageResult;
12 13 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
... ... @@ -15,21 +16,20 @@ import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
15 16 import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
16 17 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
17 18 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
18   -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleScenePageReqVO;
19   -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneSaveReqVO;
  19 +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
  20 +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
20 21 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
21   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
22 22 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
23 23 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
24 24 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
25   -import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotRuleSceneMapper;
26   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneConditionOperatorEnum;
27   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneConditionTypeEnum;
28   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum;
  25 +import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper;
  26 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
  27 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
29 28 import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager;
30 29 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
31 30 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
32 31 import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotSceneRuleAction;
  32 +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager;
33 33 import jakarta.annotation.Resource;
34 34 import lombok.extern.slf4j.Slf4j;
35 35 import org.springframework.stereotype.Service;
... ... @@ -53,200 +53,166 @@ import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.RULE_SCENE_NO
53 53 @Service
54 54 @Validated
55 55 @Slf4j
56   -public class IotRuleSceneServiceImpl implements IotRuleSceneService {
  56 +public class IotSceneRuleServiceImpl implements IotSceneRuleService {
57 57  
58 58 @Resource
59   - private IotRuleSceneMapper ruleSceneMapper;
60   -
61   - @Resource
62   - private List<IotSceneRuleAction> ruleSceneActions;
  59 + private IotSceneRuleMapper sceneRuleMapper;
63 60  
64 61 @Resource(name = "iotSchedulerManager")
65 62 private IotSchedulerManager schedulerManager;
66   -
67 63 @Resource
68 64 private IotProductService productService;
69   -
70 65 @Resource
71 66 private IotDeviceService deviceService;
  67 + @Resource
  68 + private IotSceneRuleMatcherManager matcherManager;
  69 + @Resource
  70 + private List<IotSceneRuleAction> sceneRuleActions;
72 71  
73 72 @Override
74   - public Long createRuleScene(IotRuleSceneSaveReqVO createReqVO) {
75   - IotSceneRuleDO ruleScene = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class);
76   - ruleSceneMapper.insert(ruleScene);
77   - return ruleScene.getId();
  73 + public Long createSceneRule(IotSceneRuleSaveReqVO createReqVO) {
  74 + IotSceneRuleDO sceneRule = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class);
  75 + sceneRuleMapper.insert(sceneRule);
  76 + return sceneRule.getId();
78 77 }
79 78  
80 79 @Override
81   - public void updateRuleScene(IotRuleSceneSaveReqVO updateReqVO) {
  80 + public void updateSceneRule(IotSceneRuleSaveReqVO updateReqVO) {
82 81 // 校验存在
83   - validateRuleSceneExists(updateReqVO.getId());
  82 + validateSceneRuleExists(updateReqVO.getId());
84 83 // 更新
85 84 IotSceneRuleDO updateObj = BeanUtils.toBean(updateReqVO, IotSceneRuleDO.class);
86   - ruleSceneMapper.updateById(updateObj);
  85 + sceneRuleMapper.updateById(updateObj);
87 86 }
88 87  
89 88 @Override
90   - public void updateRuleSceneStatus(Long id, Integer status) {
  89 + public void updateSceneRuleStatus(Long id, Integer status) {
91 90 // 校验存在
92   - validateRuleSceneExists(id);
  91 + validateSceneRuleExists(id);
93 92 // 更新状态
94 93 IotSceneRuleDO updateObj = new IotSceneRuleDO().setId(id).setStatus(status);
95   - ruleSceneMapper.updateById(updateObj);
  94 + sceneRuleMapper.updateById(updateObj);
96 95 }
97 96  
98 97 @Override
99   - public void deleteRuleScene(Long id) {
  98 + public void deleteSceneRule(Long id) {
100 99 // 校验存在
101   - validateRuleSceneExists(id);
  100 + validateSceneRuleExists(id);
102 101 // 删除
103   - ruleSceneMapper.deleteById(id);
  102 + sceneRuleMapper.deleteById(id);
104 103 }
105 104  
106   - private void validateRuleSceneExists(Long id) {
107   - if (ruleSceneMapper.selectById(id) == null) {
  105 + private void validateSceneRuleExists(Long id) {
  106 + if (sceneRuleMapper.selectById(id) == null) {
108 107 throw exception(RULE_SCENE_NOT_EXISTS);
109 108 }
110 109 }
111 110  
112 111 @Override
113   - public IotSceneRuleDO getRuleScene(Long id) {
114   - return ruleSceneMapper.selectById(id);
  112 + public IotSceneRuleDO getSceneRule(Long id) {
  113 + return sceneRuleMapper.selectById(id);
115 114 }
116 115  
117 116 @Override
118   - public PageResult<IotSceneRuleDO> getRuleScenePage(IotRuleScenePageReqVO pageReqVO) {
119   - return ruleSceneMapper.selectPage(pageReqVO);
  117 + public PageResult<IotSceneRuleDO> getSceneRulePage(IotSceneRulePageReqVO pageReqVO) {
  118 + return sceneRuleMapper.selectPage(pageReqVO);
120 119 }
121 120  
122 121 @Override
123   - public void validateRuleSceneList(Collection<Long> ids) {
  122 + public void validateSceneRuleList(Collection<Long> ids) {
124 123 if (CollUtil.isEmpty(ids)) {
125 124 return;
126 125 }
127 126 // 批量查询存在的规则场景
128   - List<IotSceneRuleDO> existingScenes = ruleSceneMapper.selectByIds(ids);
  127 + List<IotSceneRuleDO> existingScenes = sceneRuleMapper.selectByIds(ids);
129 128 if (existingScenes.size() != ids.size()) {
130 129 throw exception(RULE_SCENE_NOT_EXISTS);
131 130 }
132 131 }
133 132  
134 133 @Override
135   - public List<IotSceneRuleDO> getRuleSceneListByStatus(Integer status) {
136   - return ruleSceneMapper.selectListByStatus(status);
  134 + public List<IotSceneRuleDO> getSceneRuleListByStatus(Integer status) {
  135 + return sceneRuleMapper.selectListByStatus(status);
137 136 }
138 137  
139 138 // TODO 芋艿,缓存待实现
140 139 @Override
141   - @TenantIgnore // 忽略租户隔离:因为 IotRuleSceneMessageHandler 调用时,一般未传递租户,所以需要忽略
142   - public List<IotSceneRuleDO> getRuleSceneListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) {
143   - // TODO @puhui999:一些注释,看看要不要优化下;
144   - // 注意:旧的测试代码已删除,因为使用了废弃的数据结构
145   - // 如需测试,请使用上面的新结构测试代码示例
146   - List<IotSceneRuleDO> list = ruleSceneMapper.selectList();
  140 + @TenantIgnore // 忽略租户隔离:因为 IotSceneRuleMessageHandler 调用时,一般未传递租户,所以需要忽略
  141 + public List<IotSceneRuleDO> getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId) {
  142 + List<IotSceneRuleDO> list = sceneRuleMapper.selectList();
147 143 // 只返回启用状态的规则场景
148 144 List<IotSceneRuleDO> enabledList = filterList(list,
149   - ruleScene -> CommonStatusEnum.ENABLE.getStatus().equals(ruleScene.getStatus()));
  145 + sceneRule -> CommonStatusEnum.isEnable(sceneRule.getStatus()));
150 146  
151 147 // 根据 productKey 和 deviceName 进行匹配
152   - return filterList(enabledList, ruleScene -> {
153   - if (CollUtil.isEmpty(ruleScene.getTriggers())) {
  148 + return filterList(enabledList, sceneRule -> {
  149 + if (CollUtil.isEmpty(sceneRule.getTriggers())) {
154 150 return false;
155 151 }
156 152  
157   - for (IotSceneRuleDO.Trigger trigger : ruleScene.getTriggers()) {
  153 + for (IotSceneRuleDO.Trigger trigger : sceneRule.getTriggers()) {
158 154 // 检查触发器是否匹配指定的产品和设备
159   - if (isMatchProductAndDevice(trigger, productKey, deviceName)) {
160   - return true;
161   - }
162   - }
163   - return false;
164   - });
165   - }
166   -
167   - /**
168   - * 检查触发器是否匹配指定的产品和设备
169   - *
170   - * @param trigger 触发器
171   - * @param productKey 产品标识
172   - * @param deviceName 设备名称
173   - * @return 是否匹配
174   - */
175   - private boolean isMatchProductAndDevice(IotSceneRuleDO.Trigger trigger, String productKey, String deviceName) {
176   - try {
177   - // 1. 检查产品是否匹配
178   - if (trigger.getProductId() != null) {
179   - // 通过 productKey 获取产品信息
180   - IotProductDO product = productService.getProductByProductKey(productKey);
181   - if (product == null || !trigger.getProductId().equals(product.getId())) {
182   - return false;
183   - }
184   - }
185   -
186   - // 2. 检查设备是否匹配
187   - if (trigger.getDeviceId() != null) {
188   - // 通过 productKey 和 deviceName 获取设备信息
189   - IotDeviceDO device = deviceService.getDeviceFromCache(productKey, deviceName);
190   - if (device == null) {
191   - return false;
192   - }
193   -
194   - // 检查是否是全部设备的特殊标识
195   - if (IotDeviceDO.DEVICE_ID_ALL.equals(trigger.getDeviceId())) {
196   - return true; // 匹配所有设备
197   - }
198   -
199   - // 检查具体设备ID是否匹配
200   - if (!trigger.getDeviceId().equals(device.getId())) {
  155 + try {
  156 + // 1. 检查产品是否匹配
  157 + if (trigger.getProductId() == null) {
  158 + return false;
  159 + }
  160 + if (trigger.getDeviceId() == null) {
  161 + return false;
  162 + }
  163 + // 检查是否是全部设备的特殊标识
  164 + if (IotDeviceDO.DEVICE_ID_ALL.equals(trigger.getDeviceId())) {
  165 + return true; // 匹配所有设备
  166 + }
  167 + // 检查具体设备 ID 是否匹配
  168 + return ObjUtil.equal(productId, trigger.getProductId()) && ObjUtil.equal(deviceId, trigger.getDeviceId());
  169 + } catch (Exception e) {
  170 + log.warn("[isMatchProductAndDevice][产品({}) 设备({}) 匹配触发器异常]", productId, deviceId, e);
201 171 return false;
202 172 }
203 173 }
204   -
205   - return true;
206   - } catch (Exception e) {
207   - log.warn("[isMatchProductAndDevice][产品({}) 设备({}) 匹配触发器异常]", productKey, deviceName, e);
208 174 return false;
209   - }
  175 + });
210 176 }
211 177  
212 178 @Override
213   - public void executeRuleSceneByDevice(IotDeviceMessage message) {
  179 + public void executeSceneRuleByDevice(IotDeviceMessage message) {
214 180 // TODO @芋艿:这里的 tenantId,通过设备获取;
215 181 TenantUtils.execute(message.getTenantId(), () -> {
216 182 // 1. 获得设备匹配的规则场景
217   - List<IotSceneRuleDO> ruleScenes = getMatchedRuleSceneListByMessage(message);
218   - if (CollUtil.isEmpty(ruleScenes)) {
  183 + List<IotSceneRuleDO> sceneRules = getMatchedSceneRuleListByMessage(message);
  184 + if (CollUtil.isEmpty(sceneRules)) {
219 185 return;
220 186 }
221 187  
222 188 // 2. 执行规则场景
223   - executeRuleSceneAction(message, ruleScenes);
  189 + executeSceneRuleAction(message, sceneRules);
224 190 });
225 191 }
226 192  
227 193 @Override
228   - public void executeRuleSceneByTimer(Long id) {
  194 + public void executeSceneRuleByTimer(Long id) {
229 195 // 1.1 获得规则场景
230   - IotSceneRuleDO scene = TenantUtils.executeIgnore(() -> ruleSceneMapper.selectById(id));
  196 + IotSceneRuleDO scene = TenantUtils.executeIgnore(() -> sceneRuleMapper.selectById(id));
231 197 if (scene == null) {
232   - log.error("[executeRuleSceneByTimer][规则场景({}) 不存在]", id);
  198 + log.error("[executeSceneRuleByTimer][规则场景({}) 不存在]", id);
233 199 return;
234 200 }
235 201 if (CommonStatusEnum.isDisable(scene.getStatus())) {
236   - log.info("[executeRuleSceneByTimer][规则场景({}) 已被禁用]", id);
  202 + log.info("[executeSceneRuleByTimer][规则场景({}) 已被禁用]", id);
237 203 return;
238 204 }
239 205 // 1.2 判断是否有定时触发器,避免脏数据
240 206 IotSceneRuleDO.Trigger config = CollUtil.findOne(scene.getTriggers(),
241   - trigger -> ObjUtil.equals(trigger.getType(), IotRuleSceneTriggerTypeEnum.TIMER.getType()));
  207 + trigger -> ObjUtil.equals(trigger.getType(), IotSceneRuleTriggerTypeEnum.TIMER.getType()));
242 208 if (config == null) {
243   - log.error("[executeRuleSceneByTimer][规则场景({}) 不存在定时触发器]", scene);
  209 + log.error("[executeSceneRuleByTimer][规则场景({}) 不存在定时触发器]", scene);
244 210 return;
245 211 }
246 212  
247 213 // 2. 执行规则场景
248 214 TenantUtils.execute(scene.getTenantId(),
249   - () -> executeRuleSceneAction(null, ListUtil.toList(scene)));
  215 + () -> executeSceneRuleAction(null, ListUtil.toList(scene)));
250 216 }
251 217  
252 218 /**
... ... @@ -255,204 +221,132 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
255 221 * @param message 设备消息
256 222 * @return 规则场景列表
257 223 */
258   - private List<IotSceneRuleDO> getMatchedRuleSceneListByMessage(IotDeviceMessage message) {
  224 + private List<IotSceneRuleDO> getMatchedSceneRuleListByMessage(IotDeviceMessage message) {
259 225 // 1. 匹配设备
260 226 // TODO @芋艿:可能需要 getSelf(); 缓存
261 227 // 1.1 通过 deviceId 获取设备信息
262 228 IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId());
263 229 if (device == null) {
264   - log.warn("[getMatchedRuleSceneListByMessage][设备({}) 不存在]", message.getDeviceId());
  230 + log.warn("[getMatchedSceneRuleListByMessage][设备({}) 不存在]", message.getDeviceId());
265 231 return List.of();
266 232 }
267 233  
268 234 // 1.2 通过 productId 获取产品信息
269 235 IotProductDO product = productService.getProductFromCache(device.getProductId());
270 236 if (product == null) {
271   - log.warn("[getMatchedRuleSceneListByMessage][产品({}) 不存在]", device.getProductId());
  237 + log.warn("[getMatchedSceneRuleListByMessage][产品({}) 不存在]", device.getProductId());
272 238 return List.of();
273 239 }
274 240  
275 241 // 1.3 获取匹配的规则场景
276   - List<IotSceneRuleDO> ruleScenes = getRuleSceneListByProductKeyAndDeviceNameFromCache(
277   - product.getProductKey(), device.getDeviceName());
278   - if (CollUtil.isEmpty(ruleScenes)) {
279   - return ruleScenes;
  242 + List<IotSceneRuleDO> sceneRules = getSceneRuleListByProductIdAndDeviceIdFromCache(
  243 + product.getId(), device.getId());
  244 + if (CollUtil.isEmpty(sceneRules)) {
  245 + return sceneRules;
280 246 }
281 247  
282   - // 2. 匹配 trigger 触发器的条件
283   - return filterList(ruleScenes, ruleScene -> {
284   - for (IotSceneRuleDO.Trigger trigger : ruleScene.getTriggers()) {
285   - // 2.1 检查触发器类型,根据新的枚举值进行匹配
286   - // TODO @芋艿:需要根据新的触发器类型枚举进行适配
287   - // 原来使用 IotRuleSceneTriggerTypeEnum.DEVICE,新结构可能有不同的类型
288   -
289   - // 2.2 条件分组为空,说明没有匹配的条件,因此不匹配
290   - if (CollUtil.isEmpty(trigger.getConditionGroups())) {
291   - return false;
292   - }
293   -
294   - // 2.3 检查条件分组:分组与分组之间是"或"的关系,条件与条件之间是"且"的关系
295   - boolean anyGroupMatched = false;
296   - for (List<IotSceneRuleDO.TriggerCondition> conditionGroup : trigger.getConditionGroups()) {
297   - if (CollUtil.isEmpty(conditionGroup)) {
298   - continue;
299   - }
300   -
301   - // 检查当前分组中的所有条件是否都匹配(且关系)
302   - boolean allConditionsMatched = true;
303   - for (IotSceneRuleDO.TriggerCondition condition : conditionGroup) {
304   - // TODO @芋艿:这里需要实现具体的条件匹配逻辑
305   - // 根据新的 TriggerCondition 结构进行匹配
306   - if (!isTriggerConditionMatched(message, condition, ruleScene, trigger)) {
307   - allConditionsMatched = false;
308   - break;
309   - }
310   - }
311   -
312   - if (allConditionsMatched) {
313   - anyGroupMatched = true;
314   - break; // 有一个分组匹配即可
315   - }
316   - }
317   -
318   - if (anyGroupMatched) {
319   - log.info("[getMatchedRuleSceneList][消息({}) 匹配到规则场景编号({}) 的触发器({})]", message, ruleScene.getId(), trigger);
320   - return true;
321   - }
322   - }
323   - return false;
324   - });
  248 + // 2. 使用重构后的触发器匹配逻辑
  249 + return filterList(sceneRules, sceneRule -> matchSceneRuleTriggers(message, sceneRule));
325 250 }
326 251  
327 252 /**
328   - * 基于消息,判断触发器的条件是否匹配
  253 + * 匹配场景规则的所有触发器
329 254 *
330 255 * @param message 设备消息
331   - * @param condition 触发条件
332   - * @param ruleScene 规则场景(用于日志,无其它作用)
333   - * @param trigger 触发器(用于日志,无其它作用)
  256 + * @param sceneRule 场景规则
334 257 * @return 是否匹配
335 258 */
336   - private boolean isTriggerConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition,
337   - IotSceneRuleDO ruleScene, IotSceneRuleDO.Trigger trigger) {
338   - try {
339   - // 1. 根据条件类型进行匹配
340   - if (IotRuleSceneConditionTypeEnum.DEVICE_STATE.getType().equals(condition.getType())) {
341   - // 设备状态条件匹配
342   - return matchDeviceStateCondition(message, condition);
343   - } else if (IotRuleSceneConditionTypeEnum.DEVICE_PROPERTY.getType().equals(condition.getType())) {
344   - // 设备属性条件匹配
345   - return matchDevicePropertyCondition(message, condition);
346   - } else if (IotRuleSceneConditionTypeEnum.CURRENT_TIME.getType().equals(condition.getType())) {
347   - // 当前时间条件匹配
348   - return matchCurrentTimeCondition(condition);
349   - } else {
350   - log.warn("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 存在未知的条件类型({})]",
351   - ruleScene.getId(), trigger, condition.getType());
352   - return false;
353   - }
354   - } catch (Exception e) {
355   - log.error("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 条件匹配异常]",
356   - ruleScene.getId(), trigger, e);
  259 + private boolean matchSceneRuleTriggers(IotDeviceMessage message, IotSceneRuleDO sceneRule) {
  260 + if (CollUtil.isEmpty(sceneRule.getTriggers())) {
  261 + log.debug("[matchSceneRuleTriggers][规则场景({}) 没有配置触发器]", sceneRule.getId());
357 262 return false;
358 263 }
  264 +
  265 + for (IotSceneRuleDO.Trigger trigger : sceneRule.getTriggers()) {
  266 + if (matchSingleTrigger(message, trigger, sceneRule)) {
  267 + log.info("[matchSceneRuleTriggers][消息({}) 匹配到规则场景编号({}) 的触发器({})]",
  268 + message.getRequestId(), sceneRule.getId(), trigger.getType());
  269 + return true;
  270 + }
  271 + }
  272 + return false;
359 273 }
360 274  
361 275 /**
362   - * 匹配设备状态条件
  276 + * 匹配单个触发器
363 277 *
364 278 * @param message 设备消息
365   - * @param condition 触发条件
  279 + * @param trigger 触发器
  280 + * @param sceneRule 场景规则(用于日志)
366 281 * @return 是否匹配
367 282 */
368   - private boolean matchDeviceStateCondition(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
369   - // TODO @芋艿:需要根据设备状态进行匹配
370   - // 这里需要检查消息中的设备状态是否符合条件中定义的状态
371   - log.debug("[matchDeviceStateCondition][设备状态条件匹配逻辑待实现] condition: {}", condition);
372   - return false;
  283 + private boolean matchSingleTrigger(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) {
  284 + try {
  285 + // 2. 检查触发器的条件分组
  286 + return matcherManager.isMatched(message, trigger) && isTriggerConditionGroupsMatched(message, trigger, sceneRule);
  287 + } catch (Exception e) {
  288 + log.error("[matchSingleTrigger][触发器匹配异常] sceneRuleId: {}, triggerType: {}, message: {}",
  289 + sceneRule.getId(), trigger.getType(), message, e);
  290 + return false;
  291 + }
373 292 }
374 293  
375 294 /**
376   - * 匹配设备属性条件
  295 + * 检查触发器的条件分组是否匹配
377 296 *
378 297 * @param message 设备消息
379   - * @param condition 触发条件
  298 + * @param trigger 触发器
  299 + * @param sceneRule 场景规则(用于日志)
380 300 * @return 是否匹配
381 301 */
382   - private boolean matchDevicePropertyCondition(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
383   - // 1. 检查标识符是否匹配
384   - String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
385   - if (StrUtil.isBlank(condition.getIdentifier()) || !condition.getIdentifier().equals(messageIdentifier)) {
386   - return false;
  302 + private boolean isTriggerConditionGroupsMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) {
  303 + // 如果没有条件分组,则认为匹配成功(只依赖基础触发器匹配)
  304 + if (CollUtil.isEmpty(trigger.getConditionGroups())) {
  305 + return true;
387 306 }
388 307  
389   - // 2. 获取消息中的属性值
390   - Object messageValue = message.getData();
391   - if (messageValue == null) {
392   - return false;
393   - }
  308 + // 检查条件分组:分组与分组之间是"或"的关系,条件与条件之间是"且"的关系
  309 + for (List<IotSceneRuleDO.TriggerCondition> conditionGroup : trigger.getConditionGroups()) {
  310 + if (CollUtil.isEmpty(conditionGroup)) {
  311 + continue;
  312 + }
394 313  
395   - // 3. 根据操作符进行匹配
396   - return evaluateCondition(messageValue, condition.getOperator(), condition.getParam());
397   - }
  314 + // 检查当前分组中的所有条件是否都匹配(且关系)
  315 + boolean allConditionsMatched = true;
  316 + for (IotSceneRuleDO.TriggerCondition condition : conditionGroup) {
  317 + if (!isTriggerConditionMatched(message, condition, sceneRule, trigger)) {
  318 + allConditionsMatched = false;
  319 + break;
  320 + }
  321 + }
398 322  
399   - /**
400   - * 匹配当前时间条件
401   - *
402   - * @param condition 触发条件
403   - * @return 是否匹配
404   - */
405   - private boolean matchCurrentTimeCondition(IotSceneRuleDO.TriggerCondition condition) {
406   - // TODO @芋艿:需要根据当前时间进行匹配
407   - // 这里需要检查当前时间是否符合条件中定义的时间范围
408   - log.debug("[matchCurrentTimeCondition][当前时间条件匹配逻辑待实现] condition: {}", condition);
  323 + // 如果当前分组的所有条件都匹配,则整个触发器匹配成功
  324 + if (allConditionsMatched) {
  325 + return true;
  326 + }
  327 + }
  328 +
  329 + // 所有分组都不匹配
409 330 return false;
410 331 }
411 332  
412 333 /**
413   - * 评估条件是否匹配
  334 + * 基于消息,判断触发器的子条件是否匹配
414 335 *
415   - * @param sourceValue 源值(来自消息)
416   - * @param operator 操作符
417   - * @param paramValue 参数值(来自条件配置)
  336 + * @param message 设备消息
  337 + * @param condition 触发条件
  338 + * @param sceneRule 规则场景(用于日志,无其它作用)
  339 + * @param trigger 触发器(用于日志,无其它作用)
418 340 * @return 是否匹配
419 341 */
420   - private boolean evaluateCondition(Object sourceValue, String operator, String paramValue) {
  342 + private boolean isTriggerConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition,
  343 + IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) {
421 344 try {
422   - // 1. 校验操作符是否合法
423   - IotRuleSceneConditionOperatorEnum operatorEnum = IotRuleSceneConditionOperatorEnum.operatorOf(operator);
424   - if (operatorEnum == null) {
425   - log.warn("[evaluateCondition][存在错误的操作符({})]", operator);
426   - return false;
427   - }
428   -
429   - // 2.1 构建 Spring 表达式的变量
430   - Map<String, Object> springExpressionVariables = MapUtil.<String, Object>builder()
431   - .put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue)
432   - .build();
433   - // 2.2 根据操作符类型处理参数值
434   - if (StrUtil.isNotBlank(paramValue)) {
435   - // TODO @puhui999:这里是不是在 IotRuleSceneConditionOperatorEnum 加个属性;
436   - if (operatorEnum == IotRuleSceneConditionOperatorEnum.IN
437   - || operatorEnum == IotRuleSceneConditionOperatorEnum.NOT_IN
438   - || operatorEnum == IotRuleSceneConditionOperatorEnum.BETWEEN
439   - || operatorEnum == IotRuleSceneConditionOperatorEnum.NOT_BETWEEN) {
440   - // 处理多值情况
441   - List<String> paramValues = StrUtil.split(paramValue, CharPool.COMMA);
442   - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST,
443   - convertList(paramValues, NumberUtil::parseDouble));
444   - } else {
445   - // 处理单值情况
446   - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE,
447   - NumberUtil.parseDouble(paramValue));
448   - }
449   - }
450   -
451   - // 3. 计算 Spring 表达式
452   - return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables);
  345 + // 使用重构后的条件匹配管理器进行匹配
  346 + return matcherManager.isConditionMatched(message, condition);
453 347 } catch (Exception e) {
454   - log.error("[evaluateCondition][条件评估异常] sourceValue: {}, operator: {}, paramValue: {}",
455   - sourceValue, operator, paramValue, e);
  348 + log.error("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 条件匹配异常]",
  349 + sceneRule.getId(), trigger, e);
456 350 return false;
457 351 }
458 352 }
... ... @@ -464,19 +358,19 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
464 358 *
465 359 * @param message 设备消息
466 360 * @param condition 触发条件
467   - * @param ruleScene 规则场景(用于日志,无其它作用)
  361 + * @param sceneRule 规则场景(用于日志,无其它作用)
468 362 * @param trigger 触发器(用于日志,无其它作用)
469 363 * @return 是否匹配
470 364 */
471 365 @SuppressWarnings({"unchecked", "DataFlowIssue"})
472 366 private boolean isTriggerConditionParameterMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition,
473   - IotSceneRuleDO ruleScene, IotSceneRuleDO.Trigger trigger) {
  367 + IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) {
474 368 // 1.1 校验操作符是否合法
475   - IotRuleSceneConditionOperatorEnum operator =
476   - IotRuleSceneConditionOperatorEnum.operatorOf(condition.getOperator());
  369 + IotSceneRuleConditionOperatorEnum operator =
  370 + IotSceneRuleConditionOperatorEnum.operatorOf(condition.getOperator());
477 371 if (operator == null) {
478 372 log.error("[isTriggerConditionParameterMatched][规则场景编号({}) 的触发器({}) 存在错误的操作符({})]",
479   - ruleScene.getId(), trigger, condition.getOperator());
  373 + sceneRule.getId(), trigger, condition.getOperator());
480 374 return false;
481 375 }
482 376 // 1.2 校验消息是否包含对应的值
... ... @@ -488,31 +382,31 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
488 382 // 2.1 构建 Spring 表达式的变量
489 383 Map<String, Object> springExpressionVariables = new HashMap<>();
490 384 try {
491   - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, messageValue);
492   - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE, condition.getParam());
  385 + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, messageValue);
  386 + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, condition.getParam());
493 387 List<String> parameterValues = StrUtil.splitTrim(condition.getParam(), CharPool.COMMA);
494   - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues);
  388 + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues);
495 389 // 特殊:解决数字的比较。因为 Spring 是基于它的 compareTo 方法,对数字的比较存在问题!
496   - if (ObjectUtils.equalsAny(operator, IotRuleSceneConditionOperatorEnum.BETWEEN,
497   - IotRuleSceneConditionOperatorEnum.NOT_BETWEEN,
498   - IotRuleSceneConditionOperatorEnum.GREATER_THAN,
499   - IotRuleSceneConditionOperatorEnum.GREATER_THAN_OR_EQUALS,
500   - IotRuleSceneConditionOperatorEnum.LESS_THAN,
501   - IotRuleSceneConditionOperatorEnum.LESS_THAN_OR_EQUALS)
  390 + if (ObjectUtils.equalsAny(operator, IotSceneRuleConditionOperatorEnum.BETWEEN,
  391 + IotSceneRuleConditionOperatorEnum.NOT_BETWEEN,
  392 + IotSceneRuleConditionOperatorEnum.GREATER_THAN,
  393 + IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS,
  394 + IotSceneRuleConditionOperatorEnum.LESS_THAN,
  395 + IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS)
502 396 && NumberUtil.isNumber(messageValue)
503 397 && NumberUtils.isAllNumber(parameterValues)) {
504   - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_SOURCE,
  398 + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE,
505 399 NumberUtil.parseDouble(messageValue));
506   - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE,
  400 + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE,
507 401 NumberUtil.parseDouble(condition.getParam()));
508   - springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST,
  402 + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST,
509 403 convertList(parameterValues, NumberUtil::parseDouble));
510 404 }
511 405 // 2.2 计算 Spring 表达式
512 406 return (Boolean) SpringExpressionUtils.parseExpression(operator.getSpringExpression(), springExpressionVariables);
513 407 } catch (Exception e) {
514 408 log.error("[isTriggerConditionParameterMatched][消息({}) 规则场景编号({}) 的触发器({}) 的匹配表达式({}/{}) 计算异常]",
515   - message, ruleScene.getId(), trigger, operator, springExpressionVariables, e);
  409 + message, sceneRule.getId(), trigger, operator, springExpressionVariables, e);
516 410 return false;
517 411 }
518 412 }
... ... @@ -521,15 +415,15 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
521 415 * 执行规则场景的动作
522 416 *
523 417 * @param message 设备消息
524   - * @param ruleScenes 规则场景列表
  418 + * @param sceneRules 规则场景列表
525 419 */
526   - private void executeRuleSceneAction(IotDeviceMessage message, List<IotSceneRuleDO> ruleScenes) {
  420 + private void executeSceneRuleAction(IotDeviceMessage message, List<IotSceneRuleDO> sceneRules) {
527 421 // 1. 遍历规则场景
528   - ruleScenes.forEach(ruleScene -> {
  422 + sceneRules.forEach(sceneRule -> {
529 423 // 2. 遍历规则场景的动作
530   - ruleScene.getActions().forEach(actionConfig -> {
  424 + sceneRule.getActions().forEach(actionConfig -> {
531 425 // 3.1 获取对应的动作 Action 数组
532   - List<IotSceneRuleAction> actions = filterList(ruleSceneActions,
  426 + List<IotSceneRuleAction> actions = filterList(sceneRuleActions,
533 427 action -> action.getType().getType().equals(actionConfig.getType()));
534 428 if (CollUtil.isEmpty(actions)) {
535 429 return;
... ... @@ -537,16 +431,20 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
537 431 // 3.2 执行动作
538 432 actions.forEach(action -> {
539 433 try {
540   - action.execute(message, ruleScene, actionConfig);
541   - log.info("[executeRuleSceneAction][消息({}) 规则场景编号({}) 的执行动作({}) 成功]",
542   - message, ruleScene.getId(), actionConfig);
  434 + action.execute(message, sceneRule, actionConfig);
  435 + log.info("[executeSceneRuleAction][消息({}) 规则场景编号({}) 的执行动作({}) 成功]",
  436 + message, sceneRule.getId(), actionConfig);
543 437 } catch (Exception e) {
544   - log.error("[executeRuleSceneAction][消息({}) 规则场景编号({}) 的执行动作({}) 执行异常]",
545   - message, ruleScene.getId(), actionConfig, e);
  438 + log.error("[executeSceneRuleAction][消息({}) 规则场景编号({}) 的执行动作({}) 执行异常]",
  439 + message, sceneRule.getId(), actionConfig, e);
546 440 }
547 441 });
548 442 });
549 443 });
550 444 }
551 445  
  446 + private IotSceneRuleServiceImpl getSelf() {
  447 + return SpringUtil.getBean(IotSceneRuleServiceImpl.class);
  448 + }
  449 +
552 450 }
... ...
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;
5 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 6 import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO;
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
8   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
9 9 import cn.iocoder.yudao.module.iot.service.alert.IotAlertRecordService;
10 10 import jakarta.annotation.Resource;
11 11 import org.springframework.stereotype.Component;
... ... @@ -42,8 +42,8 @@ public class IotAlertRecoverSceneRuleAction implements IotSceneRuleAction {
42 42 }
43 43  
44 44 @Override
45   - public IotRuleSceneActionTypeEnum getType() {
46   - return IotRuleSceneActionTypeEnum.ALERT_RECOVER;
  45 + public IotSceneRuleActionTypeEnum getType() {
  46 + return IotSceneRuleActionTypeEnum.ALERT_RECOVER;
47 47 }
48 48  
49 49 }
... ...
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;
5 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 6 import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
8   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
9 9 import cn.iocoder.yudao.module.iot.service.alert.IotAlertConfigService;
10 10 import cn.iocoder.yudao.module.iot.service.alert.IotAlertRecordService;
11 11 import cn.iocoder.yudao.module.system.api.mail.MailSendApi;
... ... @@ -62,8 +62,8 @@ public class IotAlertTriggerSceneRuleAction implements IotSceneRuleAction {
62 62 }
63 63  
64 64 @Override
65   - public IotRuleSceneActionTypeEnum getType() {
66   - return IotRuleSceneActionTypeEnum.ALERT_TRIGGER;
  65 + public IotSceneRuleActionTypeEnum getType() {
  66 + return IotSceneRuleActionTypeEnum.ALERT_TRIGGER;
67 67 }
68 68  
69 69 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlRuleSceneAction.java renamed to 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;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
4 4 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
5   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
  5 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
6 6 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
7 7 import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
8 8 import jakarta.annotation.Resource;
... ... @@ -16,7 +16,7 @@ import org.springframework.stereotype.Component;
16 16 */
17 17 @Component
18 18 @Slf4j
19   -public class IotDeviceControlRuleSceneAction implements IotSceneRuleAction {
  19 +public class IotDeviceControlSceneRuleAction implements IotSceneRuleAction {
20 20  
21 21 @Resource
22 22 private IotDeviceService deviceService;
... ... @@ -48,8 +48,8 @@ public class IotDeviceControlRuleSceneAction implements IotSceneRuleAction {
48 48 }
49 49  
50 50 @Override
51   - public IotRuleSceneActionTypeEnum getType() {
52   - return IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET;
  51 + public IotSceneRuleActionTypeEnum getType() {
  52 + return IotSceneRuleActionTypeEnum.DEVICE_PROPERTY_SET;
53 53 }
54 54  
55 55 }
... ...
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;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
4 4 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
5   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
  5 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
6 6  
7 7 import javax.annotation.Nullable;
8 8  
... ... @@ -31,6 +31,6 @@ public interface IotSceneRuleAction {
31 31 *
32 32 * @return 类型
33 33 */
34   - IotRuleSceneActionTypeEnum getType();
  34 + IotSceneRuleActionTypeEnum getType();
35 35  
36 36 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleMatcher.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
  2 +
  3 +import cn.hutool.core.util.NumberUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
  6 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +
  11 +import java.util.HashMap;
  12 +import java.util.List;
  13 +import java.util.Map;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  16 +
  17 +/**
  18 + * IoT 场景规则匹配器抽象基类
  19 + * <p>
  20 + * 提供通用的条件评估逻辑和工具方法,支持触发器和条件两种匹配类型
  21 + *
  22 + * @author HUIHUI
  23 + */
  24 +@Slf4j
  25 +public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher {
  26 +
  27 + /**
  28 + * 评估条件是否匹配
  29 + *
  30 + * @param sourceValue 源值(来自消息)
  31 + * @param operator 操作符
  32 + * @param paramValue 参数值(来自条件配置)
  33 + * @return 是否匹配
  34 + */
  35 + protected boolean evaluateCondition(Object sourceValue, String operator, String paramValue) {
  36 + try {
  37 + // 1. 校验操作符是否合法
  38 + IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator);
  39 + if (operatorEnum == null) {
  40 + log.warn("[evaluateCondition][存在错误的操作符({})]", operator);
  41 + return false;
  42 + }
  43 +
  44 + // 2. 构建 Spring 表达式变量
  45 + Map<String, Object> springExpressionVariables = new HashMap<>();
  46 + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue);
  47 +
  48 + // 处理参数值
  49 + if (StrUtil.isNotBlank(paramValue)) {
  50 + // 处理多值情况(如 IN、BETWEEN 操作符)
  51 + if (paramValue.contains(",")) {
  52 + List<String> paramValues = StrUtil.split(paramValue, ',');
  53 + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST,
  54 + convertList(paramValues, NumberUtil::parseDouble));
  55 + } else {
  56 + // 处理单值情况
  57 + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE,
  58 + NumberUtil.parseDouble(paramValue));
  59 + }
  60 + }
  61 +
  62 + // 3. 计算 Spring 表达式
  63 + return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables);
  64 + } catch (Exception e) {
  65 + log.error("[evaluateCondition][条件评估异常] sourceValue: {}, operator: {}, paramValue: {}",
  66 + sourceValue, operator, paramValue, e);
  67 + return false;
  68 + }
  69 + }
  70 +
  71 + // ========== 触发器相关工具方法 ==========
  72 +
  73 + /**
  74 + * 检查基础触发器参数是否有效
  75 + *
  76 + * @param trigger 触发器配置
  77 + * @return 是否有效
  78 + */
  79 + protected boolean isBasicTriggerValid(IotSceneRuleDO.Trigger trigger) {
  80 + return trigger != null && trigger.getType() != null;
  81 + }
  82 +
  83 + /**
  84 + * 检查触发器操作符和值是否有效
  85 + *
  86 + * @param trigger 触发器配置
  87 + * @return 是否有效
  88 + */
  89 + protected boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) {
  90 + return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue());
  91 + }
  92 +
  93 + /**
  94 + * 记录触发器匹配成功日志
  95 + *
  96 + * @param message 设备消息
  97 + * @param trigger 触发器配置
  98 + */
  99 + protected void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
  100 + log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType());
  101 + }
  102 +
  103 + /**
  104 + * 记录触发器匹配失败日志
  105 + *
  106 + * @param message 设备消息
  107 + * @param trigger 触发器配置
  108 + * @param reason 失败原因
  109 + */
  110 + protected void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) {
  111 + log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason);
  112 + }
  113 +
  114 + // ========== 条件相关工具方法 ==========
  115 +
  116 + /**
  117 + * 检查基础条件参数是否有效
  118 + *
  119 + * @param condition 触发条件
  120 + * @return 是否有效
  121 + */
  122 + protected boolean isBasicConditionValid(IotSceneRuleDO.TriggerCondition condition) {
  123 + return condition != null && condition.getType() != null;
  124 + }
  125 +
  126 + /**
  127 + * 检查条件操作符和参数是否有效
  128 + *
  129 + * @param condition 触发条件
  130 + * @return 是否有效
  131 + */
  132 + protected boolean isConditionOperatorAndParamValid(IotSceneRuleDO.TriggerCondition condition) {
  133 + return StrUtil.isNotBlank(condition.getOperator()) && StrUtil.isNotBlank(condition.getParam());
  134 + }
  135 +
  136 + /**
  137 + * 记录条件匹配成功日志
  138 + *
  139 + * @param message 设备消息
  140 + * @param condition 触发条件
  141 + */
  142 + protected void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
  143 + log.debug("[{}][消息({}) 匹配条件({}) 成功]", getMatcherName(), message.getRequestId(), condition.getType());
  144 + }
  145 +
  146 + /**
  147 + * 记录条件匹配失败日志
  148 + *
  149 + * @param message 设备消息
  150 + * @param condition 触发条件
  151 + * @param reason 失败原因
  152 + */
  153 + protected void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) {
  154 + log.debug("[{}][消息({}) 匹配条件({}) 失败: {}]", getMatcherName(), message.getRequestId(), condition.getType(), reason);
  155 + }
  156 +
  157 + // ========== 通用工具方法 ==========
  158 +
  159 + /**
  160 + * 检查标识符是否匹配
  161 + *
  162 + * @param expectedIdentifier 期望的标识符
  163 + * @param actualIdentifier 实际的标识符
  164 + * @return 是否匹配
  165 + */
  166 + protected boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) {
  167 + return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier);
  168 + }
  169 +
  170 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  6 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import org.springframework.stereotype.Component;
  9 +
  10 +import java.time.LocalDateTime;
  11 +import java.time.LocalTime;
  12 +import java.time.format.DateTimeFormatter;
  13 +
  14 +/**
  15 + * 当前时间条件匹配器
  16 + * <p>
  17 + * 处理时间相关的子条件匹配逻辑
  18 + *
  19 + * @author HUIHUI
  20 + */
  21 +@Component
  22 +@Slf4j
  23 +public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher {
  24 +
  25 + /**
  26 + * 时间格式化器 - HH:mm:ss
  27 + */
  28 + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
  29 +
  30 + /**
  31 + * 时间格式化器 - HH:mm
  32 + */
  33 + private static final DateTimeFormatter TIME_FORMATTER_SHORT = DateTimeFormatter.ofPattern("HH:mm");
  34 +
  35 + @Override
  36 + public MatcherType getMatcherType() {
  37 + return MatcherType.CONDITION;
  38 + }
  39 +
  40 + @Override
  41 + public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
  42 + return IotSceneRuleConditionTypeEnum.CURRENT_TIME;
  43 + }
  44 +
  45 + @Override
  46 + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
  47 + // 1. 基础参数校验
  48 + if (!isBasicConditionValid(condition)) {
  49 + logConditionMatchFailure(message, condition, "条件基础参数无效");
  50 + return false;
  51 + }
  52 +
  53 + // 2. 检查操作符和参数是否有效
  54 + if (!isConditionOperatorAndParamValid(condition)) {
  55 + logConditionMatchFailure(message, condition, "操作符或参数无效");
  56 + return false;
  57 + }
  58 +
  59 + // 3. 获取当前时间
  60 + LocalDateTime now = LocalDateTime.now();
  61 +
  62 + // 4. 根据操作符类型进行不同的时间匹配
  63 + String operator = condition.getOperator();
  64 + String param = condition.getParam();
  65 +
  66 + boolean matched = false;
  67 +
  68 + try {
  69 + if (operator.startsWith("date_time_")) {
  70 + // 日期时间匹配(时间戳)
  71 + matched = matchDateTime(now, operator, param);
  72 + } else if (operator.startsWith("time_")) {
  73 + // 当日时间匹配(HH:mm:ss)
  74 + matched = matchTime(now.toLocalTime(), operator, param);
  75 + } else {
  76 + // 其他操作符,使用通用条件评估器
  77 + matched = evaluateCondition(now.toEpochSecond(java.time.ZoneOffset.of("+8")), operator, param);
  78 + }
  79 +
  80 + if (matched) {
  81 + logConditionMatchSuccess(message, condition);
  82 + } else {
  83 + logConditionMatchFailure(message, condition, "时间条件不匹配");
  84 + }
  85 +
  86 + } catch (Exception e) {
  87 + log.error("[CurrentTimeConditionMatcher][时间条件匹配异常] operator: {}, param: {}", operator, param, e);
  88 + logConditionMatchFailure(message, condition, "时间条件匹配异常: " + e.getMessage());
  89 + matched = false;
  90 + }
  91 +
  92 + return matched;
  93 + }
  94 +
  95 + /**
  96 + * 匹配日期时间(时间戳)
  97 + */
  98 + private boolean matchDateTime(LocalDateTime now, String operator, String param) {
  99 + long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8"));
  100 + return evaluateCondition(currentTimestamp, operator.substring("date_time_".length()), param);
  101 + }
  102 +
  103 + /**
  104 + * 匹配当日时间(HH:mm:ss)
  105 + */
  106 + private boolean matchTime(LocalTime currentTime, String operator, String param) {
  107 + try {
  108 + String actualOperator = operator.substring("time_".length());
  109 +
  110 + if ("between".equals(actualOperator)) {
  111 + // 时间区间匹配
  112 + String[] timeRange = param.split(",");
  113 + if (timeRange.length != 2) {
  114 + return false;
  115 + }
  116 +
  117 + LocalTime startTime = parseTime(timeRange[0].trim());
  118 + LocalTime endTime = parseTime(timeRange[1].trim());
  119 +
  120 + return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime);
  121 + } else {
  122 + // 单个时间比较
  123 + LocalTime targetTime = parseTime(param);
  124 +
  125 + switch (actualOperator) {
  126 + case ">":
  127 + return currentTime.isAfter(targetTime);
  128 + case "<":
  129 + return currentTime.isBefore(targetTime);
  130 + case ">=":
  131 + return !currentTime.isBefore(targetTime);
  132 + case "<=":
  133 + return !currentTime.isAfter(targetTime);
  134 + case "=":
  135 + return currentTime.equals(targetTime);
  136 + default:
  137 + return false;
  138 + }
  139 + }
  140 + } catch (Exception e) {
  141 + log.error("[CurrentTimeConditionMatcher][时间解析异常] param: {}", param, e);
  142 + return false;
  143 + }
  144 + }
  145 +
  146 + /**
  147 + * 解析时间字符串
  148 + */
  149 + private LocalTime parseTime(String timeStr) {
  150 + if (StrUtil.isBlank(timeStr)) {
  151 + throw new IllegalArgumentException("时间字符串不能为空");
  152 + }
  153 +
  154 + // 尝试不同的时间格式
  155 + try {
  156 + if (timeStr.length() == 5) { // HH:mm
  157 + return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT);
  158 + } else { // HH:mm:ss
  159 + return LocalTime.parse(timeStr, TIME_FORMATTER);
  160 + }
  161 + } catch (Exception e) {
  162 + throw new IllegalArgumentException("时间格式无效: " + timeStr, e);
  163 + }
  164 + }
  165 +
  166 + @Override
  167 + public int getPriority() {
  168 + return 40; // 较低优先级
  169 + }
  170 +
  171 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  5 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  6 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  9 +import org.springframework.stereotype.Component;
  10 +
  11 +/**
  12 + * 设备事件上报触发器匹配器
  13 + * <p>
  14 + * 处理设备事件上报的触发器匹配逻辑
  15 + *
  16 + * @author HUIHUI
  17 + */
  18 +@Component
  19 +public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher {
  20 +
  21 + /**
  22 + * 设备事件上报消息方法
  23 + */
  24 + private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod();
  25 +
  26 + @Override
  27 + public MatcherType getMatcherType() {
  28 + return MatcherType.TRIGGER;
  29 + }
  30 +
  31 + @Override
  32 + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
  33 + return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST;
  34 + }
  35 +
  36 + @Override
  37 + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
  38 + // 1. 基础参数校验
  39 + if (!isBasicTriggerValid(trigger)) {
  40 + logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
  41 + return false;
  42 + }
  43 +
  44 + // 2. 检查消息方法是否匹配
  45 + if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) {
  46 + logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod());
  47 + return false;
  48 + }
  49 +
  50 + // 3. 检查标识符是否匹配
  51 + String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
  52 + if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
  53 + logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
  54 + return false;
  55 + }
  56 +
  57 + // 4. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配
  58 + // 但如果配置了操作符和值,则需要进行条件匹配
  59 + if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) {
  60 + Object eventData = message.getData();
  61 + if (eventData == null) {
  62 + logTriggerMatchFailure(message, trigger, "消息中事件数据为空");
  63 + return false;
  64 + }
  65 +
  66 + boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue());
  67 + if (!matched) {
  68 + logTriggerMatchFailure(message, trigger, "事件数据条件不匹配");
  69 + return false;
  70 + }
  71 + }
  72 +
  73 + logTriggerMatchSuccess(message, trigger);
  74 + return true;
  75 + }
  76 +
  77 + @Override
  78 + public int getPriority() {
  79 + return 30; // 中等优先级
  80 + }
  81 +
  82 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  4 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  6 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +/**
  10 + * 设备属性条件匹配器
  11 + * <p>
  12 + * 处理设备属性相关的子条件匹配逻辑
  13 + *
  14 + * @author HUIHUI
  15 + */
  16 +@Component
  17 +public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher {
  18 +
  19 + @Override
  20 + public MatcherType getMatcherType() {
  21 + return MatcherType.CONDITION;
  22 + }
  23 +
  24 + @Override
  25 + public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
  26 + return IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY;
  27 + }
  28 +
  29 + @Override
  30 + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
  31 + // 1. 基础参数校验
  32 + if (!isBasicConditionValid(condition)) {
  33 + logConditionMatchFailure(message, condition, "条件基础参数无效");
  34 + return false;
  35 + }
  36 +
  37 + // 2. 检查标识符是否匹配
  38 + String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
  39 + if (!isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) {
  40 + logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier);
  41 + return false;
  42 + }
  43 +
  44 + // 3. 检查操作符和参数是否有效
  45 + if (!isConditionOperatorAndParamValid(condition)) {
  46 + logConditionMatchFailure(message, condition, "操作符或参数无效");
  47 + return false;
  48 + }
  49 +
  50 + // 4. 获取属性值
  51 + Object propertyValue = message.getData();
  52 + if (propertyValue == null) {
  53 + logConditionMatchFailure(message, condition, "消息中属性值为空");
  54 + return false;
  55 + }
  56 +
  57 + // 5. 使用条件评估器进行匹配
  58 + boolean matched = evaluateCondition(propertyValue, condition.getOperator(), condition.getParam());
  59 +
  60 + if (matched) {
  61 + logConditionMatchSuccess(message, condition);
  62 + } else {
  63 + logConditionMatchFailure(message, condition, "设备属性条件不匹配");
  64 + }
  65 +
  66 + return matched;
  67 + }
  68 +
  69 + @Override
  70 + public int getPriority() {
  71 + return 25; // 中等优先级
  72 + }
  73 +
  74 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  7 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  8 +import org.springframework.stereotype.Component;
  9 +
  10 +/**
  11 + * 设备属性上报触发器匹配器
  12 + * <p>
  13 + * 处理设备属性数据上报的触发器匹配逻辑
  14 + *
  15 + * @author HUIHUI
  16 + */
  17 +@Component
  18 +public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatcher {
  19 +
  20 + /**
  21 + * 设备属性上报消息方法
  22 + */
  23 + private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod();
  24 +
  25 + @Override
  26 + public MatcherType getMatcherType() {
  27 + return MatcherType.TRIGGER;
  28 + }
  29 +
  30 + @Override
  31 + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
  32 + return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST;
  33 + }
  34 +
  35 + @Override
  36 + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
  37 + // 1. 基础参数校验
  38 + if (!isBasicTriggerValid(trigger)) {
  39 + logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
  40 + return false;
  41 + }
  42 +
  43 + // 2. 检查消息方法是否匹配
  44 + if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) {
  45 + logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod());
  46 + return false;
  47 + }
  48 +
  49 + // 3. 检查标识符是否匹配
  50 + String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
  51 + if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
  52 + logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
  53 + return false;
  54 + }
  55 +
  56 + // 4. 检查操作符和值是否有效
  57 + if (!isTriggerOperatorAndValueValid(trigger)) {
  58 + logTriggerMatchFailure(message, trigger, "操作符或值无效");
  59 + return false;
  60 + }
  61 +
  62 + // 5. 获取属性值
  63 + Object propertyValue = message.getData();
  64 + if (propertyValue == null) {
  65 + logTriggerMatchFailure(message, trigger, "消息中属性值为空");
  66 + return false;
  67 + }
  68 +
  69 + // 6. 使用条件评估器进行匹配
  70 + boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue());
  71 +
  72 + if (matched) {
  73 + logTriggerMatchSuccess(message, trigger);
  74 + } else {
  75 + logTriggerMatchFailure(message, trigger, "属性值条件不匹配");
  76 + }
  77 +
  78 + return matched;
  79 + }
  80 +
  81 + @Override
  82 + public int getPriority() {
  83 + return 20; // 中等优先级
  84 + }
  85 +
  86 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  7 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  8 +import org.springframework.stereotype.Component;
  9 +
  10 +/**
  11 + * 设备服务调用触发器匹配器
  12 + * <p>
  13 + * 处理设备服务调用的触发器匹配逻辑
  14 + *
  15 + * @author HUIHUI
  16 + */
  17 +@Component
  18 +public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatcher {
  19 +
  20 + /**
  21 + * 设备服务调用消息方法
  22 + */
  23 + private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod();
  24 +
  25 + @Override
  26 + public MatcherType getMatcherType() {
  27 + return MatcherType.TRIGGER;
  28 + }
  29 +
  30 + @Override
  31 + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
  32 + return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE;
  33 + }
  34 +
  35 + @Override
  36 + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
  37 + // 1. 基础参数校验
  38 + if (!isBasicTriggerValid(trigger)) {
  39 + logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
  40 + return false;
  41 + }
  42 +
  43 + // 2. 检查消息方法是否匹配
  44 + if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) {
  45 + logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod());
  46 + return false;
  47 + }
  48 +
  49 + // 3. 检查标识符是否匹配
  50 + String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
  51 + if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
  52 + logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
  53 + return false;
  54 + }
  55 +
  56 + // 4. 对于服务调用触发器,通常只需要匹配服务标识符即可
  57 + // 不需要检查操作符和值,因为服务调用本身就是触发条件
  58 +
  59 + logTriggerMatchSuccess(message, trigger);
  60 + return true;
  61 + }
  62 +
  63 + @Override
  64 + public int getPriority() {
  65 + return 40; // 较低优先级
  66 + }
  67 +
  68 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  4 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  5 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
  6 +import org.springframework.stereotype.Component;
  7 +
  8 +/**
  9 + * 设备状态条件匹配器
  10 + * <p>
  11 + * 处理设备状态相关的子条件匹配逻辑
  12 + *
  13 + * @author HUIHUI
  14 + */
  15 +@Component
  16 +public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher {
  17 +
  18 + @Override
  19 + public MatcherType getMatcherType() {
  20 + return MatcherType.CONDITION;
  21 + }
  22 +
  23 + @Override
  24 + public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
  25 + return IotSceneRuleConditionTypeEnum.DEVICE_STATE;
  26 + }
  27 +
  28 + @Override
  29 + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
  30 + // 1. 基础参数校验
  31 + if (!isBasicConditionValid(condition)) {
  32 + logConditionMatchFailure(message, condition, "条件基础参数无效");
  33 + return false;
  34 + }
  35 +
  36 + // 2. 检查操作符和参数是否有效
  37 + if (!isConditionOperatorAndParamValid(condition)) {
  38 + logConditionMatchFailure(message, condition, "操作符或参数无效");
  39 + return false;
  40 + }
  41 +
  42 + // 3. 获取设备状态值
  43 + // 设备状态通常在消息的 data 字段中
  44 + Object stateValue = message.getData();
  45 + if (stateValue == null) {
  46 + logConditionMatchFailure(message, condition, "消息中设备状态值为空");
  47 + return false;
  48 + }
  49 +
  50 + // 4. 使用条件评估器进行匹配
  51 + boolean matched = evaluateCondition(stateValue, condition.getOperator(), condition.getParam());
  52 +
  53 + if (matched) {
  54 + logConditionMatchSuccess(message, condition);
  55 + } else {
  56 + logConditionMatchFailure(message, condition, "设备状态条件不匹配");
  57 + }
  58 +
  59 + return matched;
  60 + }
  61 +
  62 + @Override
  63 + public int getPriority() {
  64 + return 30; // 中等优先级
  65 + }
  66 +
  67 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  6 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +/**
  10 + * 设备状态更新触发器匹配器
  11 + * <p>
  12 + * 处理设备上下线状态变更的触发器匹配逻辑
  13 + *
  14 + * @author HUIHUI
  15 + */
  16 +@Component
  17 +public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher {
  18 +
  19 + /**
  20 + * 设备状态更新消息方法
  21 + */
  22 + private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod();
  23 +
  24 + @Override
  25 + public MatcherType getMatcherType() {
  26 + return MatcherType.TRIGGER;
  27 + }
  28 +
  29 + @Override
  30 + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
  31 + return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE;
  32 + }
  33 +
  34 + @Override
  35 + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
  36 + // 1. 基础参数校验
  37 + if (!isBasicTriggerValid(trigger)) {
  38 + logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
  39 + return false;
  40 + }
  41 +
  42 + // 2. 检查消息方法是否匹配
  43 + if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) {
  44 + logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod());
  45 + return false;
  46 + }
  47 +
  48 + // 3. 检查操作符和值是否有效
  49 + if (!isTriggerOperatorAndValueValid(trigger)) {
  50 + logTriggerMatchFailure(message, trigger, "操作符或值无效");
  51 + return false;
  52 + }
  53 +
  54 + // 4. 获取设备状态值
  55 + Object stateValue = message.getData();
  56 + if (stateValue == null) {
  57 + logTriggerMatchFailure(message, trigger, "消息中设备状态值为空");
  58 + return false;
  59 + }
  60 +
  61 + // 5. 使用条件评估器进行匹配
  62 + boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue());
  63 +
  64 + if (matched) {
  65 + logTriggerMatchSuccess(message, trigger);
  66 + } else {
  67 + logTriggerMatchFailure(message, trigger, "状态值条件不匹配");
  68 + }
  69 +
  70 + return matched;
  71 + }
  72 +
  73 + @Override
  74 + public int getPriority() {
  75 + return 10; // 高优先级
  76 + }
  77 +
  78 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  4 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  5 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
  6 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  7 +
  8 +/**
  9 + * IoT 场景规则匹配器统一接口
  10 + * <p>
  11 + * 支持触发器匹配和条件匹配两种类型,遵循策略模式设计
  12 + * <p>
  13 + * 匹配器类型说明:
  14 + * - 触发器匹配器:用于匹配主触发条件(如设备消息类型、定时器等)
  15 + * - 条件匹配器:用于匹配子条件(如设备状态、属性值、时间条件等)
  16 + *
  17 + * @author HUIHUI
  18 + */
  19 +public interface IotSceneRuleMatcher {
  20 +
  21 + /**
  22 + * 匹配器类型枚举
  23 + */
  24 + enum MatcherType {
  25 + /**
  26 + * 触发器匹配器 - 用于匹配主触发条件
  27 + */
  28 + TRIGGER,
  29 + /**
  30 + * 条件匹配器 - 用于匹配子条件
  31 + */
  32 + CONDITION
  33 + }
  34 +
  35 + /**
  36 + * 获取匹配器类型
  37 + *
  38 + * @return 匹配器类型
  39 + */
  40 + MatcherType getMatcherType();
  41 +
  42 + /**
  43 + * 获取支持的触发器类型(仅触发器匹配器需要实现)
  44 + *
  45 + * @return 触发器类型枚举,条件匹配器返回 null
  46 + */
  47 + default IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
  48 + return null;
  49 + }
  50 +
  51 + /**
  52 + * 获取支持的条件类型(仅条件匹配器需要实现)
  53 + *
  54 + * @return 条件类型枚举,触发器匹配器返回 null
  55 + */
  56 + default IotSceneRuleConditionTypeEnum getSupportedConditionType() {
  57 + return null;
  58 + }
  59 +
  60 + /**
  61 + * 检查触发器是否匹配消息(仅触发器匹配器需要实现)
  62 + *
  63 + * @param message 设备消息
  64 + * @param trigger 触发器配置
  65 + * @return 是否匹配
  66 + */
  67 + default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
  68 + throw new UnsupportedOperationException("触发器匹配方法仅支持触发器匹配器");
  69 + }
  70 +
  71 + /**
  72 + * 检查条件是否匹配消息(仅条件匹配器需要实现)
  73 + *
  74 + * @param message 设备消息
  75 + * @param condition 触发条件
  76 + * @return 是否匹配
  77 + */
  78 + default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
  79 + throw new UnsupportedOperationException("条件匹配方法仅支持条件匹配器");
  80 + }
  81 +
  82 + /**
  83 + * 获取匹配优先级(数值越小优先级越高)
  84 + * <p>
  85 + * 用于在多个匹配器支持同一类型时确定优先级
  86 + *
  87 + * @return 优先级数值
  88 + */
  89 + default int getPriority() {
  90 + return 100;
  91 + }
  92 +
  93 + /**
  94 + * 获取匹配器名称,用于日志和调试
  95 + *
  96 + * @return 匹配器名称
  97 + */
  98 + default String getMatcherName() {
  99 + return this.getClass().getSimpleName();
  100 + }
  101 +
  102 + /**
  103 + * 是否启用该匹配器
  104 + * <p>
  105 + * 可用于动态开关某些匹配器
  106 + *
  107 + * @return 是否启用
  108 + */
  109 + default boolean isEnabled() {
  110 + return true;
  111 + }
  112 +
  113 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  6 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
  7 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import org.springframework.stereotype.Component;
  10 +
  11 +import java.util.*;
  12 +import java.util.function.Function;
  13 +import java.util.stream.Collectors;
  14 +
  15 +import static cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum.findTriggerTypeEnum;
  16 +
  17 +/**
  18 + * IoT 场景规则匹配器统一管理器
  19 + * <p>
  20 + * 负责管理所有匹配器(触发器匹配器和条件匹配器),并提供统一的匹配入口
  21 + *
  22 + * @author HUIHUI
  23 + */
  24 +@Component
  25 +@Slf4j
  26 +public class IotSceneRuleMatcherManager {
  27 +
  28 + /**
  29 + * 触发器匹配器映射表
  30 + * Key: 触发器类型枚举
  31 + * Value: 对应的匹配器实例
  32 + */
  33 + private final Map<IotSceneRuleTriggerTypeEnum, IotSceneRuleMatcher> triggerMatcherMap;
  34 +
  35 + /**
  36 + * 条件匹配器映射表
  37 + * Key: 条件类型枚举
  38 + * Value: 对应的匹配器实例
  39 + */
  40 + private final Map<IotSceneRuleConditionTypeEnum, IotSceneRuleMatcher> conditionMatcherMap;
  41 +
  42 + /**
  43 + * 所有匹配器列表(按优先级排序)
  44 + */
  45 + private final List<IotSceneRuleMatcher> allMatchers;
  46 +
  47 + public IotSceneRuleMatcherManager(List<IotSceneRuleMatcher> matchers) {
  48 + if (CollUtil.isEmpty(matchers)) {
  49 + log.warn("[IotSceneRuleMatcherManager][没有找到任何匹配器]");
  50 + this.triggerMatcherMap = new HashMap<>();
  51 + this.conditionMatcherMap = new HashMap<>();
  52 + this.allMatchers = new ArrayList<>();
  53 + return;
  54 + }
  55 +
  56 + // 按优先级排序并过滤启用的匹配器
  57 + this.allMatchers = matchers.stream()
  58 + .filter(IotSceneRuleMatcher::isEnabled)
  59 + .sorted(Comparator.comparing(IotSceneRuleMatcher::getPriority))
  60 + .collect(Collectors.toList());
  61 +
  62 + // 分离触发器匹配器和条件匹配器
  63 + List<IotSceneRuleMatcher> triggerMatchers = this.allMatchers.stream()
  64 + .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.TRIGGER)
  65 + .toList();
  66 +
  67 + List<IotSceneRuleMatcher> conditionMatchers = this.allMatchers.stream()
  68 + .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION)
  69 + .toList();
  70 +
  71 + // 构建触发器匹配器映射表
  72 + this.triggerMatcherMap = triggerMatchers.stream()
  73 + .collect(Collectors.toMap(
  74 + IotSceneRuleMatcher::getSupportedTriggerType,
  75 + Function.identity(),
  76 + (existing, replacement) -> {
  77 + log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]",
  78 + existing.getSupportedTriggerType(),
  79 + existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName());
  80 + return existing.getPriority() <= replacement.getPriority() ? existing : replacement;
  81 + },
  82 + LinkedHashMap::new
  83 + ));
  84 +
  85 + // 构建条件匹配器映射表
  86 + this.conditionMatcherMap = conditionMatchers.stream()
  87 + .collect(Collectors.toMap(
  88 + IotSceneRuleMatcher::getSupportedConditionType,
  89 + Function.identity(),
  90 + (existing, replacement) -> {
  91 + log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]",
  92 + existing.getSupportedConditionType(),
  93 + existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName());
  94 + return existing.getPriority() <= replacement.getPriority() ? existing : replacement;
  95 + },
  96 + LinkedHashMap::new
  97 + ));
  98 +
  99 + log.info("[IotSceneRuleMatcherManager][初始化完成,共加载 {} 个匹配器,其中触发器匹配器 {} 个,条件匹配器 {} 个]",
  100 + this.allMatchers.size(), this.triggerMatcherMap.size(), this.conditionMatcherMap.size());
  101 +
  102 + // 记录触发器匹配器详情
  103 + this.triggerMatcherMap.forEach((type, matcher) ->
  104 + log.info("[IotSceneRuleMatcherManager][触发器匹配器] 类型: {}, 匹配器: {}, 优先级: {}",
  105 + type, matcher.getMatcherName(), matcher.getPriority()));
  106 +
  107 + // 记录条件匹配器详情
  108 + this.conditionMatcherMap.forEach((type, matcher) ->
  109 + log.info("[IotSceneRuleMatcherManager][条件匹配器] 类型: {}, 匹配器: {}, 优先级: {}",
  110 + type, matcher.getMatcherName(), matcher.getPriority()));
  111 + }
  112 +
  113 + /**
  114 + * 检查触发器是否匹配消息(主条件匹配)
  115 + *
  116 + * @param message 设备消息
  117 + * @param trigger 触发器配置
  118 + * @return 是否匹配
  119 + */
  120 + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
  121 + if (message == null || trigger == null || trigger.getType() == null) {
  122 + log.debug("[isMatched][参数无效] message: {}, trigger: {}", message, trigger);
  123 + return false;
  124 + }
  125 +
  126 + // 根据触发器类型查找对应的匹配器
  127 + IotSceneRuleTriggerTypeEnum triggerType = findTriggerTypeEnum(trigger.getType());
  128 + if (triggerType == null) {
  129 + log.warn("[isMatched][未知的触发器类型: {}]", trigger.getType());
  130 + return false;
  131 + }
  132 +
  133 + IotSceneRuleMatcher matcher = triggerMatcherMap.get(triggerType);
  134 + if (matcher == null) {
  135 + log.warn("[isMatched][触发器类型({})没有对应的匹配器]", triggerType);
  136 + return false;
  137 + }
  138 +
  139 + try {
  140 + return matcher.isMatched(message, trigger);
  141 + } catch (Exception e) {
  142 + log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}, matcher: {}",
  143 + message, trigger, matcher.getMatcherName(), e);
  144 + return false;
  145 + }
  146 + }
  147 +
  148 + /**
  149 + * 检查子条件是否匹配消息
  150 + *
  151 + * @param message 设备消息
  152 + * @param condition 触发条件
  153 + * @return 是否匹配
  154 + */
  155 + public boolean isConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
  156 + if (message == null || condition == null || condition.getType() == null) {
  157 + log.debug("[isConditionMatched][参数无效] message: {}, condition: {}", message, condition);
  158 + return false;
  159 + }
  160 +
  161 + // 根据条件类型查找对应的匹配器
  162 + IotSceneRuleConditionTypeEnum conditionType = findConditionTypeEnum(condition.getType());
  163 + if (conditionType == null) {
  164 + log.warn("[isConditionMatched][未知的条件类型: {}]", condition.getType());
  165 + return false;
  166 + }
  167 +
  168 + IotSceneRuleMatcher matcher = conditionMatcherMap.get(conditionType);
  169 + if (matcher == null) {
  170 + log.warn("[isConditionMatched][条件类型({})没有对应的匹配器]", conditionType);
  171 + return false;
  172 + }
  173 +
  174 + try {
  175 + return matcher.isMatched(message, condition);
  176 + } catch (Exception e) {
  177 + log.error("[isConditionMatched][条件匹配异常] message: {}, condition: {}, matcher: {}",
  178 + message, condition, matcher.getMatcherName(), e);
  179 + return false;
  180 + }
  181 + }
  182 +
  183 + /**
  184 + * 根据类型值查找条件类型枚举
  185 + *
  186 + * @param typeValue 类型值
  187 + * @return 条件类型枚举
  188 + */
  189 + private IotSceneRuleConditionTypeEnum findConditionTypeEnum(Integer typeValue) {
  190 + return Arrays.stream(IotSceneRuleConditionTypeEnum.values())
  191 + .filter(type -> type.getType().equals(typeValue))
  192 + .findFirst()
  193 + .orElse(null);
  194 + }
  195 +
  196 + /**
  197 + * 获取所有支持的触发器类型
  198 + *
  199 + * @return 支持的触发器类型列表
  200 + */
  201 + public Set<IotSceneRuleTriggerTypeEnum> getSupportedTriggerTypes() {
  202 + return new HashSet<>(triggerMatcherMap.keySet());
  203 + }
  204 +
  205 + /**
  206 + * 获取所有支持的条件类型
  207 + *
  208 + * @return 支持的条件类型列表
  209 + */
  210 + public Set<IotSceneRuleConditionTypeEnum> getSupportedConditionTypes() {
  211 + return new HashSet<>(conditionMatcherMap.keySet());
  212 + }
  213 +
  214 + /**
  215 + * 获取指定触发器类型的匹配器
  216 + *
  217 + * @param triggerType 触发器类型
  218 + * @return 匹配器实例,如果不存在则返回 null
  219 + */
  220 + public IotSceneRuleMatcher getTriggerMatcher(IotSceneRuleTriggerTypeEnum triggerType) {
  221 + return triggerMatcherMap.get(triggerType);
  222 + }
  223 +
  224 + /**
  225 + * 获取指定条件类型的匹配器
  226 + *
  227 + * @param conditionType 条件类型
  228 + * @return 匹配器实例,如果不存在则返回 null
  229 + */
  230 + public IotSceneRuleMatcher getConditionMatcher(IotSceneRuleConditionTypeEnum conditionType) {
  231 + return conditionMatcherMap.get(conditionType);
  232 + }
  233 +
  234 + /**
  235 + * 获取所有匹配器的统计信息
  236 + *
  237 + * @return 统计信息映射表
  238 + */
  239 + public Map<String, Object> getMatcherStatistics() {
  240 + Map<String, Object> statistics = new HashMap<>();
  241 + statistics.put("totalMatchers", allMatchers.size());
  242 + statistics.put("triggerMatchers", triggerMatcherMap.size());
  243 + statistics.put("conditionMatchers", conditionMatcherMap.size());
  244 + statistics.put("supportedTriggerTypes", getSupportedTriggerTypes());
  245 + statistics.put("supportedConditionTypes", getSupportedConditionTypes());
  246 +
  247 + // 触发器匹配器详情
  248 + Map<String, Object> triggerMatcherDetails = new HashMap<>();
  249 + triggerMatcherMap.forEach((type, matcher) -> {
  250 + Map<String, Object> detail = new HashMap<>();
  251 + detail.put("matcherName", matcher.getMatcherName());
  252 + detail.put("priority", matcher.getPriority());
  253 + detail.put("enabled", matcher.isEnabled());
  254 + triggerMatcherDetails.put(type.name(), detail);
  255 + });
  256 + statistics.put("triggerMatcherDetails", triggerMatcherDetails);
  257 +
  258 + // 条件匹配器详情
  259 + Map<String, Object> conditionMatcherDetails = new HashMap<>();
  260 + conditionMatcherMap.forEach((type, matcher) -> {
  261 + Map<String, Object> detail = new HashMap<>();
  262 + detail.put("matcherName", matcher.getMatcherName());
  263 + detail.put("priority", matcher.getPriority());
  264 + detail.put("enabled", matcher.isEnabled());
  265 + conditionMatcherDetails.put(type.name(), detail);
  266 + });
  267 + statistics.put("conditionMatcherDetails", conditionMatcherDetails);
  268 +
  269 + return statistics;
  270 + }
  271 +
  272 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  6 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +/**
  10 + * 定时触发器匹配器
  11 + * <p>
  12 + * 处理定时触发的触发器匹配逻辑
  13 + * 注意:定时触发器不依赖设备消息,主要用于定时任务场景
  14 + *
  15 + * @author HUIHUI
  16 + */
  17 +@Component
  18 +public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher {
  19 +
  20 + @Override
  21 + public MatcherType getMatcherType() {
  22 + return MatcherType.TRIGGER;
  23 + }
  24 +
  25 + @Override
  26 + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
  27 + return IotSceneRuleTriggerTypeEnum.TIMER;
  28 + }
  29 +
  30 + @Override
  31 + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
  32 + // 1. 基础参数校验
  33 + if (!isBasicTriggerValid(trigger)) {
  34 + logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
  35 + return false;
  36 + }
  37 +
  38 + // 2. 检查 CRON 表达式是否存在
  39 + if (StrUtil.isBlank(trigger.getCronExpression())) {
  40 + logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式");
  41 + return false;
  42 + }
  43 +
  44 + // 3. 定时触发器通常不依赖具体的设备消息
  45 + // 它是通过定时任务调度器触发的,这里主要是验证配置的有效性
  46 +
  47 + // 4. 可以添加 CRON 表达式格式验证
  48 + if (!isValidCronExpression(trigger.getCronExpression())) {
  49 + logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression());
  50 + return false;
  51 + }
  52 +
  53 + logTriggerMatchSuccess(message, trigger);
  54 + return true;
  55 + }
  56 +
  57 + /**
  58 + * 验证 CRON 表达式格式是否有效
  59 + *
  60 + * @param cronExpression CRON 表达式
  61 + * @return 是否有效
  62 + */
  63 + private boolean isValidCronExpression(String cronExpression) {
  64 + try {
  65 + // 简单的 CRON 表达式格式验证
  66 + // 标准 CRON 表达式应该有 6 或 7 个字段(秒 分 时 日 月 周 [年])
  67 + String[] fields = cronExpression.trim().split("\\s+");
  68 + return fields.length >= 6 && fields.length <= 7;
  69 + } catch (Exception e) {
  70 + return false;
  71 + }
  72 + }
  73 +
  74 + @Override
  75 + public int getPriority() {
  76 + return 50; // 最低优先级,因为定时触发器不依赖消息
  77 + }
  78 +
  79 + @Override
  80 + public boolean isEnabled() {
  81 + // 定时触发器可以根据配置动态启用/禁用
  82 + return true;
  83 + }
  84 +
  85 +}
... ...
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 {
85 85 }
86 86  
87 87 @Test
88   - public void testRedisStreamDataBridge() throws Exception {
  88 + public void testRedisDataBridge() throws Exception {
89 89 // 1. 创建执行器实例
90   - IotRedisStreamRuleAction action = new IotRedisStreamRuleAction();
  90 + IotRedisRuleAction action = new IotRedisRuleAction();
91 91  
92   - // 2. 创建配置
93   - IotDataSinkRedisStreamConfig config = new IotDataSinkRedisStreamConfig()
94   - .setHost("127.0.0.1")
95   - .setPort(6379)
96   - .setDatabase(0)
97   - .setPassword("123456")
98   - .setTopic("test-stream");
  92 + // 2. 创建配置 - 测试 Stream 数据结构
  93 + IotDataSinkRedisConfig config = new IotDataSinkRedisConfig();
  94 + config.setHost("127.0.0.1");
  95 + config.setPort(6379);
  96 + config.setDatabase(0);
  97 + config.setPassword("123456");
  98 + config.setTopic("test-stream");
  99 + config.setDataStructure(1); // Stream 类型
99 100  
100 101 // 3. 执行测试并验证缓存
101   - executeAndVerifyCache(action, config, "RedisStream");
  102 + executeAndVerifyCache(action, config, "Redis");
102 103 }
103 104  
104 105 @Test
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotRuleSceneServiceSimpleTest.java renamed to 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;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 4 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
5   -import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneSaveReqVO;
  5 +import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
6 6 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
7   -import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotRuleSceneMapper;
  7 +import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper;
8 8 import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager;
9 9 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
10 10 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
... ... @@ -24,21 +24,21 @@ import static org.mockito.ArgumentMatchers.any;
24 24 import static org.mockito.Mockito.*;
25 25  
26 26 /**
27   - * {@link IotRuleSceneServiceImpl} 的简化单元测试类
  27 + * {@link IotSceneRuleServiceImpl} 的简化单元测试类
28 28 * 使用 Mockito 进行纯单元测试,不依赖 Spring 容器
29 29 *
30 30 * @author 芋道源码
31 31 */
32   -public class IotRuleSceneServiceSimpleTest extends BaseMockitoUnitTest {
  32 +public class IotSceneRuleServiceSimpleTest extends BaseMockitoUnitTest {
33 33  
34 34 @InjectMocks
35   - private IotRuleSceneServiceImpl ruleSceneService;
  35 + private IotSceneRuleServiceImpl sceneRuleService;
36 36  
37 37 @Mock
38   - private IotRuleSceneMapper ruleSceneMapper;
  38 + private IotSceneRuleMapper sceneRuleMapper;
39 39  
40 40 @Mock
41   - private List<IotSceneRuleAction> ruleSceneActions;
  41 + private List<IotSceneRuleAction> sceneRuleActions;
42 42  
43 43 @Mock
44 44 private IotSchedulerManager schedulerManager;
... ... @@ -50,9 +50,9 @@ public class IotRuleSceneServiceSimpleTest extends BaseMockitoUnitTest {
50 50 private IotDeviceService deviceService;
51 51  
52 52 @Test
53   - public void testCreateRuleScene_success() {
  53 + public void testCreateScene_Rule_success() {
54 54 // 准备参数
55   - IotRuleSceneSaveReqVO createReqVO = randomPojo(IotRuleSceneSaveReqVO.class, o -> {
  55 + IotSceneRuleSaveReqVO createReqVO = randomPojo(IotSceneRuleSaveReqVO.class, o -> {
56 56 o.setId(null);
57 57 o.setStatus(CommonStatusEnum.ENABLE.getStatus());
58 58 o.setTriggers(Collections.singletonList(randomPojo(IotSceneRuleDO.Trigger.class)));
... ... @@ -61,25 +61,25 @@ public class IotRuleSceneServiceSimpleTest extends BaseMockitoUnitTest {
61 61  
62 62 // Mock 行为
63 63 Long expectedId = randomLongId();
64   - when(ruleSceneMapper.insert(any(IotSceneRuleDO.class))).thenAnswer(invocation -> {
65   - IotSceneRuleDO ruleScene = invocation.getArgument(0);
66   - ruleScene.setId(expectedId);
  64 + when(sceneRuleMapper.insert(any(IotSceneRuleDO.class))).thenAnswer(invocation -> {
  65 + IotSceneRuleDO sceneRule = invocation.getArgument(0);
  66 + sceneRule.setId(expectedId);
67 67 return 1;
68 68 });
69 69  
70 70 // 调用
71   - Long ruleSceneId = ruleSceneService.createRuleScene(createReqVO);
  71 + Long sceneRuleId = sceneRuleService.createSceneRule(createReqVO);
72 72  
73 73 // 断言
74   - assertEquals(expectedId, ruleSceneId);
75   - verify(ruleSceneMapper, times(1)).insert(any(IotSceneRuleDO.class));
  74 + assertEquals(expectedId, sceneRuleId);
  75 + verify(sceneRuleMapper, times(1)).insert(any(IotSceneRuleDO.class));
76 76 }
77 77  
78 78 @Test
79   - public void testUpdateRuleScene_success() {
  79 + public void testUpdateScene_Rule_success() {
80 80 // 准备参数
81 81 Long id = randomLongId();
82   - IotRuleSceneSaveReqVO updateReqVO = randomPojo(IotRuleSceneSaveReqVO.class, o -> {
  82 + IotSceneRuleSaveReqVO updateReqVO = randomPojo(IotSceneRuleSaveReqVO.class, o -> {
83 83 o.setId(id);
84 84 o.setStatus(CommonStatusEnum.ENABLE.getStatus());
85 85 o.setTriggers(Collections.singletonList(randomPojo(IotSceneRuleDO.Trigger.class)));
... ... @@ -87,125 +87,125 @@ public class IotRuleSceneServiceSimpleTest extends BaseMockitoUnitTest {
87 87 });
88 88  
89 89 // Mock 行为
90   - IotSceneRuleDO existingRuleScene = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
91   - when(ruleSceneMapper.selectById(id)).thenReturn(existingRuleScene);
92   - when(ruleSceneMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1);
  90 + IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
  91 + when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule);
  92 + when(sceneRuleMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1);
93 93  
94 94 // 调用
95   - assertDoesNotThrow(() -> ruleSceneService.updateRuleScene(updateReqVO));
  95 + assertDoesNotThrow(() -> sceneRuleService.updateSceneRule(updateReqVO));
96 96  
97 97 // 验证
98   - verify(ruleSceneMapper, times(1)).selectById(id);
99   - verify(ruleSceneMapper, times(1)).updateById(any(IotSceneRuleDO.class));
  98 + verify(sceneRuleMapper, times(1)).selectById(id);
  99 + verify(sceneRuleMapper, times(1)).updateById(any(IotSceneRuleDO.class));
100 100 }
101 101  
102 102 @Test
103   - public void testDeleteRuleScene_success() {
  103 + public void testDeleteSceneRule_success() {
104 104 // 准备参数
105 105 Long id = randomLongId();
106 106  
107 107 // Mock 行为
108   - IotSceneRuleDO existingRuleScene = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
109   - when(ruleSceneMapper.selectById(id)).thenReturn(existingRuleScene);
110   - when(ruleSceneMapper.deleteById(id)).thenReturn(1);
  108 + IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
  109 + when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule);
  110 + when(sceneRuleMapper.deleteById(id)).thenReturn(1);
111 111  
112 112 // 调用
113   - assertDoesNotThrow(() -> ruleSceneService.deleteRuleScene(id));
  113 + assertDoesNotThrow(() -> sceneRuleService.deleteSceneRule(id));
114 114  
115 115 // 验证
116   - verify(ruleSceneMapper, times(1)).selectById(id);
117   - verify(ruleSceneMapper, times(1)).deleteById(id);
  116 + verify(sceneRuleMapper, times(1)).selectById(id);
  117 + verify(sceneRuleMapper, times(1)).deleteById(id);
118 118 }
119 119  
120 120 @Test
121   - public void testGetRuleScene() {
  121 + public void testGetSceneRule() {
122 122 // 准备参数
123 123 Long id = randomLongId();
124   - IotSceneRuleDO expectedRuleScene = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
  124 + IotSceneRuleDO expectedSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
125 125  
126 126 // Mock 行为
127   - when(ruleSceneMapper.selectById(id)).thenReturn(expectedRuleScene);
  127 + when(sceneRuleMapper.selectById(id)).thenReturn(expectedSceneRule);
128 128  
129 129 // 调用
130   - IotSceneRuleDO result = ruleSceneService.getRuleScene(id);
  130 + IotSceneRuleDO result = sceneRuleService.getSceneRule(id);
131 131  
132 132 // 断言
133   - assertEquals(expectedRuleScene, result);
134   - verify(ruleSceneMapper, times(1)).selectById(id);
  133 + assertEquals(expectedSceneRule, result);
  134 + verify(sceneRuleMapper, times(1)).selectById(id);
135 135 }
136 136  
137 137 @Test
138   - public void testUpdateRuleSceneStatus_success() {
  138 + public void testUpdateSceneRuleStatus_success() {
139 139 // 准备参数
140 140 Long id = randomLongId();
141 141 Integer status = CommonStatusEnum.DISABLE.getStatus();
142 142  
143 143 // Mock 行为
144   - IotSceneRuleDO existingRuleScene = randomPojo(IotSceneRuleDO.class, o -> {
  144 + IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> {
145 145 o.setId(id);
146 146 o.setStatus(CommonStatusEnum.ENABLE.getStatus());
147 147 });
148   - when(ruleSceneMapper.selectById(id)).thenReturn(existingRuleScene);
149   - when(ruleSceneMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1);
  148 + when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule);
  149 + when(sceneRuleMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1);
150 150  
151 151 // 调用
152   - assertDoesNotThrow(() -> ruleSceneService.updateRuleSceneStatus(id, status));
  152 + assertDoesNotThrow(() -> sceneRuleService.updateSceneRuleStatus(id, status));
153 153  
154 154 // 验证
155   - verify(ruleSceneMapper, times(1)).selectById(id);
156   - verify(ruleSceneMapper, times(1)).updateById(any(IotSceneRuleDO.class));
  155 + verify(sceneRuleMapper, times(1)).selectById(id);
  156 + verify(sceneRuleMapper, times(1)).updateById(any(IotSceneRuleDO.class));
157 157 }
158 158  
159 159 @Test
160   - public void testExecuteRuleSceneByTimer_success() {
  160 + public void testExecuteSceneRuleByTimer_success() {
161 161 // 准备参数
162 162 Long id = randomLongId();
163 163  
164 164 // Mock 行为
165   - IotSceneRuleDO ruleScene = randomPojo(IotSceneRuleDO.class, o -> {
  165 + IotSceneRuleDO sceneRule = randomPojo(IotSceneRuleDO.class, o -> {
166 166 o.setId(id);
167 167 o.setStatus(CommonStatusEnum.ENABLE.getStatus());
168 168 });
169   - when(ruleSceneMapper.selectById(id)).thenReturn(ruleScene);
  169 + when(sceneRuleMapper.selectById(id)).thenReturn(sceneRule);
170 170  
171 171 // 调用
172   - assertDoesNotThrow(() -> ruleSceneService.executeRuleSceneByTimer(id));
  172 + assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id));
173 173  
174 174 // 验证
175   - verify(ruleSceneMapper, times(1)).selectById(id);
  175 + verify(sceneRuleMapper, times(1)).selectById(id);
176 176 }
177 177  
178 178 @Test
179   - public void testExecuteRuleSceneByTimer_notExists() {
  179 + public void testExecuteSceneRuleByTimer_notExists() {
180 180 // 准备参数
181 181 Long id = randomLongId();
182 182  
183 183 // Mock 行为
184   - when(ruleSceneMapper.selectById(id)).thenReturn(null);
  184 + when(sceneRuleMapper.selectById(id)).thenReturn(null);
185 185  
186 186 // 调用 - 不存在的场景规则应该不会抛异常,只是记录日志
187   - assertDoesNotThrow(() -> ruleSceneService.executeRuleSceneByTimer(id));
  187 + assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id));
188 188  
189 189 // 验证
190   - verify(ruleSceneMapper, times(1)).selectById(id);
  190 + verify(sceneRuleMapper, times(1)).selectById(id);
191 191 }
192 192  
193 193 @Test
194   - public void testExecuteRuleSceneByTimer_disabled() {
  194 + public void testExecuteSceneRuleByTimer_disabled() {
195 195 // 准备参数
196 196 Long id = randomLongId();
197 197  
198 198 // Mock 行为
199   - IotSceneRuleDO ruleScene = randomPojo(IotSceneRuleDO.class, o -> {
  199 + IotSceneRuleDO sceneRule = randomPojo(IotSceneRuleDO.class, o -> {
200 200 o.setId(id);
201 201 o.setStatus(CommonStatusEnum.DISABLE.getStatus());
202 202 });
203   - when(ruleSceneMapper.selectById(id)).thenReturn(ruleScene);
  203 + when(sceneRuleMapper.selectById(id)).thenReturn(sceneRule);
204 204  
205 205 // 调用 - 禁用的场景规则应该不会执行,只是记录日志
206   - assertDoesNotThrow(() -> ruleSceneService.executeRuleSceneByTimer(id));
  206 + assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id));
207 207  
208 208 // 验证
209   - verify(ruleSceneMapper, times(1)).selectById(id);
  209 + verify(sceneRuleMapper, times(1)).selectById(id);
210 210 }
211 211 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  6 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
  7 +import org.junit.jupiter.api.BeforeEach;
  8 +import org.junit.jupiter.api.Test;
  9 +
  10 +import java.util.Arrays;
  11 +import java.util.HashMap;
  12 +import java.util.List;
  13 +
  14 +import static org.junit.jupiter.api.Assertions.*;
  15 +
  16 +/**
  17 + * IoT 场景规则触发器匹配器测试类
  18 + *
  19 + * @author HUIHUI
  20 + */
  21 +public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest {
  22 +
  23 + private IotSceneRuleMatcherManager matcherManager;
  24 +
  25 + @BeforeEach
  26 + void setUp() {
  27 + // 创建所有匹配器实例
  28 + List<IotSceneRuleMatcher> matchers = Arrays.asList(
  29 + new DeviceStateUpdateTriggerMatcher(),
  30 + new DevicePropertyPostTriggerMatcher(),
  31 + new DeviceEventPostTriggerMatcher(),
  32 + new DeviceServiceInvokeTriggerMatcher(),
  33 + new TimerTriggerMatcher()
  34 + );
  35 +
  36 + // 初始化匹配器管理器
  37 + matcherManager = new IotSceneRuleMatcherManager(matchers);
  38 + }
  39 +
  40 + @Test
  41 + void testDeviceStateUpdateTriggerMatcher() {
  42 + // 1. 准备测试数据
  43 + IotDeviceMessage message = IotDeviceMessage.builder()
  44 + .requestId("test-001")
  45 + .method("thing.state.update")
  46 + .data(1) // 在线状态
  47 + .build();
  48 +
  49 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  50 + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType());
  51 + trigger.setOperator("=");
  52 + trigger.setValue("1");
  53 +
  54 + // 2. 执行测试
  55 + boolean matched = matcherManager.isMatched(message, trigger);
  56 +
  57 + // 3. 验证结果
  58 + assertTrue(matched, "设备状态更新触发器应该匹配");
  59 + }
  60 +
  61 + @Test
  62 + void testDevicePropertyPostTriggerMatcher() {
  63 + // 1. 准备测试数据
  64 + HashMap<String, Object> params = new HashMap<>();
  65 + IotDeviceMessage message = IotDeviceMessage.builder()
  66 + .requestId("test-002")
  67 + .method("thing.property.post")
  68 + .data(25.5) // 温度值
  69 + .params(params)
  70 + .build();
  71 + // 模拟标识符
  72 + params.put("identifier", "temperature");
  73 +
  74 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  75 + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType());
  76 + trigger.setIdentifier("temperature");
  77 + trigger.setOperator(">");
  78 + trigger.setValue("20");
  79 +
  80 + // 2. 执行测试
  81 + boolean matched = matcherManager.isMatched(message, trigger);
  82 +
  83 + // 3. 验证结果
  84 + assertTrue(matched, "设备属性上报触发器应该匹配");
  85 + }
  86 +
  87 + @Test
  88 + void testDeviceEventPostTriggerMatcher() {
  89 + // 1. 准备测试数据
  90 + HashMap<String, Object> params = new HashMap<>();
  91 + IotDeviceMessage message = IotDeviceMessage.builder()
  92 + .requestId("test-003")
  93 + .method("thing.event.post")
  94 + .data("alarm_data")
  95 + .params(params)
  96 + .build();
  97 + // 模拟标识符
  98 + params.put("identifier", "high_temperature_alarm");
  99 +
  100 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  101 + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType());
  102 + trigger.setIdentifier("high_temperature_alarm");
  103 +
  104 + // 2. 执行测试
  105 + boolean matched = matcherManager.isMatched(message, trigger);
  106 +
  107 + // 3. 验证结果
  108 + assertTrue(matched, "设备事件上报触发器应该匹配");
  109 + }
  110 +
  111 + @Test
  112 + void testDeviceServiceInvokeTriggerMatcher() {
  113 + // 1. 准备测试数据
  114 + HashMap<String, Object> params = new HashMap<>();
  115 + IotDeviceMessage message = IotDeviceMessage.builder()
  116 + .requestId("test-004")
  117 + .method("thing.service.invoke")
  118 + .msg("alarm_data")
  119 + .params(params)
  120 + .build();
  121 + // 模拟标识符
  122 + params.put("identifier", "restart_device");
  123 +
  124 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  125 + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
  126 + trigger.setIdentifier("restart_device");
  127 +
  128 + // 2. 执行测试
  129 + boolean matched = matcherManager.isMatched(message, trigger);
  130 +
  131 + // 3. 验证结果
  132 + assertTrue(matched, "设备服务调用触发器应该匹配");
  133 + }
  134 +
  135 + @Test
  136 + void testTimerTriggerMatcher() {
  137 + // 1. 准备测试数据
  138 + IotDeviceMessage message = IotDeviceMessage.builder()
  139 + .requestId("test-005")
  140 + .method("timer.trigger") // 定时触发器不依赖具体消息方法
  141 + .build();
  142 +
  143 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  144 + trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
  145 + trigger.setCronExpression("0 0 12 * * ?"); // 每天中午12点
  146 +
  147 + // 2. 执行测试
  148 + boolean matched = matcherManager.isMatched(message, trigger);
  149 +
  150 + // 3. 验证结果
  151 + assertTrue(matched, "定时触发器应该匹配");
  152 + }
  153 +
  154 + @Test
  155 + void testInvalidTriggerType() {
  156 + // 1. 准备测试数据
  157 + IotDeviceMessage message = IotDeviceMessage.builder()
  158 + .requestId("test-006")
  159 + .method("unknown.method")
  160 + .build();
  161 +
  162 + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
  163 + trigger.setType(999); // 无效的触发器类型
  164 +
  165 + // 2. 执行测试
  166 + boolean matched = matcherManager.isMatched(message, trigger);
  167 +
  168 + // 3. 验证结果
  169 + assertFalse(matched, "无效的触发器类型应该不匹配");
  170 + }
  171 +
  172 + @Test
  173 + void testMatcherManagerStatistics() {
  174 + // 1. 执行测试
  175 + var statistics = matcherManager.getMatcherStatistics();
  176 +
  177 + // 2. 验证结果
  178 + assertNotNull(statistics);
  179 + assertEquals(5, statistics.get("totalMatchers"));
  180 + assertEquals(5, statistics.get("enabledMatchers"));
  181 + assertNotNull(statistics.get("supportedTriggerTypes"));
  182 + assertNotNull(statistics.get("matcherDetails"));
  183 + }
  184 +
  185 + @Test
  186 + void testGetSupportedTriggerTypes() {
  187 + // 1. 执行测试
  188 + var supportedTypes = matcherManager.getSupportedTriggerTypes();
  189 +
  190 + // 2. 验证结果
  191 + assertNotNull(supportedTypes);
  192 + assertEquals(5, supportedTypes.size());
  193 + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE));
  194 + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST));
  195 + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST));
  196 + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE));
  197 + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.TIMER));
  198 + }
  199 +
  200 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/clean.sql
1   --- TODO @puhui999:sql 格式
2   --- IoT 模块测试数据清理脚本
3   -DELETE
4   -FROM "iot_scene_rule";
5   -DELETE
6   -FROM "iot_product";
7   -DELETE
8   -FROM "iot_device";
9   -DELETE
10   -FROM "iot_thing_model";
11   -DELETE
12   -FROM "iot_device_data";
13   -DELETE
14   -FROM "iot_alert_config";
15   -DELETE
16   -FROM "iot_alert_record";
17   -DELETE
18   -FROM "iot_ota_firmware";
19   -DELETE
20   -FROM "iot_ota_task";
21   -DELETE
22   -FROM "iot_ota_record";
  1 +DELETE FROM "iot_scene_rule";
  2 +DELETE FROM "iot_product";
  3 +DELETE FROM "iot_device";
  4 +DELETE FROM "iot_thing_model";
  5 +DELETE FROM "iot_device_data";
  6 +DELETE FROM "iot_alert_config";
  7 +DELETE FROM "iot_alert_record";
  8 +DELETE FROM "iot_ota_firmware";
  9 +DELETE FROM "iot_ota_task";
  10 +DELETE FROM "iot_ota_record";
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql
1   --- TODO @puhui999:sql 格式
2   --- IoT 模块测试数据库表结构
3   --- 基于 H2 数据库语法,兼容 MySQL 模式
4   -
5   --- IoT 场景联动规则表
6   -CREATE TABLE IF NOT EXISTS "iot_scene_rule"
7   -(
8   - "id"
9   - bigint
10   - NOT
11   - NULL
12   - GENERATED
13   - BY
14   - DEFAULT AS
15   - IDENTITY,
16   - "name"
17   - varchar
18   -(
19   - 255
20   -) NOT NULL DEFAULT '',
21   - "description" varchar
22   -(
23   - 500
24   -) DEFAULT NULL,
  1 +CREATE TABLE IF NOT EXISTS "iot_scene_rule" (
  2 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  3 + "name" varchar(255) NOT NULL DEFAULT '',
  4 + "description" varchar(500) DEFAULT NULL,
25 5 "status" tinyint NOT NULL DEFAULT '0',
26 6 "triggers" text,
27 7 "actions" text,
28   - "creator" varchar
29   -(
30   - 64
31   -) DEFAULT '',
  8 + "creator" varchar(64) DEFAULT '',
32 9 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
33   - "updater" varchar
34   -(
35   - 64
36   -) DEFAULT '',
  10 + "updater" varchar(64) DEFAULT '',
37 11 "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
38 12 "deleted" bit NOT NULL DEFAULT FALSE,
39 13 "tenant_id" bigint NOT NULL DEFAULT '0',
40   - PRIMARY KEY
41   -(
42   - "id"
43   -)
44   - ) COMMENT 'IoT 场景联动规则表';
  14 + PRIMARY KEY ("id")
  15 +) COMMENT 'IoT 场景联动规则表';
45 16  
46   --- IoT 产品表
47   -CREATE TABLE IF NOT EXISTS "iot_product"
48   -(
49   - "id"
50   - bigint
51   - NOT
52   - NULL
53   - GENERATED
54   - BY
55   - DEFAULT AS
56   - IDENTITY,
57   - "name"
58   - varchar
59   -(
60   - 255
61   -) NOT NULL DEFAULT '',
62   - "product_key" varchar
63   -(
64   - 100
65   -) NOT NULL DEFAULT '',
  17 +CREATE TABLE IF NOT EXISTS "iot_product" (
  18 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  19 + "name" varchar(255) NOT NULL DEFAULT '',
  20 + "product_key" varchar(100) NOT NULL DEFAULT '',
66 21 "protocol_type" tinyint NOT NULL DEFAULT '0',
67 22 "category_id" bigint DEFAULT NULL,
68   - "description" varchar
69   -(
70   - 500
71   -) DEFAULT NULL,
  23 + "description" varchar(500) DEFAULT NULL,
72 24 "data_format" tinyint NOT NULL DEFAULT '0',
73 25 "device_type" tinyint NOT NULL DEFAULT '0',
74 26 "net_type" tinyint NOT NULL DEFAULT '0',
75 27 "validate_type" tinyint NOT NULL DEFAULT '0',
76 28 "status" tinyint NOT NULL DEFAULT '0',
77   - "creator" varchar
78   -(
79   - 64
80   -) DEFAULT '',
  29 + "creator" varchar(64) DEFAULT '',
81 30 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
82   - "updater" varchar
83   -(
84   - 64
85   -) DEFAULT '',
  31 + "updater" varchar(64) DEFAULT '',
86 32 "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
87 33 "deleted" bit NOT NULL DEFAULT FALSE,
88 34 "tenant_id" bigint NOT NULL DEFAULT '0',
89   - PRIMARY KEY
90   -(
91   - "id"
92   -)
93   - ) COMMENT 'IoT 产品表';
  35 + PRIMARY KEY ("id")
  36 +) COMMENT 'IoT 产品表';
94 37  
95   --- IoT 设备表
96   -CREATE TABLE IF NOT EXISTS "iot_device"
97   -(
98   - "id"
99   - bigint
100   - NOT
101   - NULL
102   - GENERATED
103   - BY
104   - DEFAULT AS
105   - IDENTITY,
106   - "device_name"
107   - varchar
108   -(
109   - 255
110   -) NOT NULL DEFAULT '',
  38 +CREATE TABLE IF NOT EXISTS "iot_device" (
  39 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  40 + "device_name" varchar(255) NOT NULL DEFAULT '',
111 41 "product_id" bigint NOT NULL,
112   - "device_key" varchar
113   -(
114   - 100
115   -) NOT NULL DEFAULT '',
116   - "device_secret" varchar
117   -(
118   - 100
119   -) NOT NULL DEFAULT '',
120   - "nickname" varchar
121   -(
122   - 255
123   -) DEFAULT NULL,
  42 + "device_key" varchar(100) NOT NULL DEFAULT '',
  43 + "device_secret" varchar(100) NOT NULL DEFAULT '',
  44 + "nickname" varchar(255) DEFAULT NULL,
124 45 "status" tinyint NOT NULL DEFAULT '0',
125 46 "status_last_update_time" timestamp DEFAULT NULL,
126 47 "last_online_time" timestamp DEFAULT NULL,
127 48 "last_offline_time" timestamp DEFAULT NULL,
128 49 "active_time" timestamp DEFAULT NULL,
129   - "ip" varchar
130   -(
131   - 50
132   -) DEFAULT NULL,
133   - "firmware_version" varchar
134   -(
135   - 50
136   -) DEFAULT NULL,
  50 + "ip" varchar(50) DEFAULT NULL,
  51 + "firmware_version" varchar(50) DEFAULT NULL,
137 52 "device_type" tinyint NOT NULL DEFAULT '0',
138 53 "gateway_id" bigint DEFAULT NULL,
139 54 "sub_device_count" int NOT NULL DEFAULT '0',
140   - "creator" varchar
141   -(
142   - 64
143   -) DEFAULT '',
  55 + "creator" varchar(64) DEFAULT '',
144 56 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
145   - "updater" varchar
146   -(
147   - 64
148   -) DEFAULT '',
  57 + "updater" varchar(64) DEFAULT '',
149 58 "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
150 59 "deleted" bit NOT NULL DEFAULT FALSE,
151 60 "tenant_id" bigint NOT NULL DEFAULT '0',
152   - PRIMARY KEY
153   -(
154   - "id"
155   -)
156   - ) COMMENT 'IoT 设备表';
  61 + PRIMARY KEY ("id")
  62 +) COMMENT 'IoT 设备表';
157 63  
158   --- IoT 物模型表
159   -CREATE TABLE IF NOT EXISTS "iot_thing_model"
160   -(
161   - "id"
162   - bigint
163   - NOT
164   - NULL
165   - GENERATED
166   - BY
167   - DEFAULT AS
168   - IDENTITY,
169   - "product_id"
170   - bigint
171   - NOT
172   - NULL,
173   - "identifier"
174   - varchar
175   -(
176   - 100
177   -) NOT NULL DEFAULT '',
178   - "name" varchar
179   -(
180   - 255
181   -) NOT NULL DEFAULT '',
182   - "description" varchar
183   -(
184   - 500
185   -) DEFAULT NULL,
  64 +CREATE TABLE IF NOT EXISTS "iot_thing_model" (
  65 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  66 + "product_id" bigint NOT NULL,
  67 + "identifier" varchar(100) NOT NULL DEFAULT '',
  68 + "name" varchar(255) NOT NULL DEFAULT '',
  69 + "description" varchar(500) DEFAULT NULL,
186 70 "type" tinyint NOT NULL DEFAULT '1',
187 71 "property" text,
188   - "creator" varchar
189   -(
190   - 64
191   -) DEFAULT '',
  72 + "creator" varchar(64) DEFAULT '',
192 73 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
193   - "updater" varchar
194   -(
195   - 64
196   -) DEFAULT '',
  74 + "updater" varchar(64) DEFAULT '',
197 75 "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
198 76 "deleted" bit NOT NULL DEFAULT FALSE,
199 77 "tenant_id" bigint NOT NULL DEFAULT '0',
200   - PRIMARY KEY
201   -(
202   - "id"
203   -)
204   - ) COMMENT 'IoT 物模型表';
  78 + PRIMARY KEY ("id")
  79 +) COMMENT 'IoT 物模型表';
205 80  
206   --- IoT 设备数据表
207   -CREATE TABLE IF NOT EXISTS "iot_device_data"
208   -(
209   - "id"
210   - bigint
211   - NOT
212   - NULL
213   - GENERATED
214   - BY
215   - DEFAULT AS
216   - IDENTITY,
217   - "device_id"
218   - bigint
219   - NOT
220   - NULL,
221   - "product_id"
222   - bigint
223   - NOT
224   - NULL,
225   - "identifier"
226   - varchar
227   -(
228   - 100
229   -) NOT NULL DEFAULT '',
  81 +CREATE TABLE IF NOT EXISTS "iot_device_data" (
  82 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  83 + "device_id" bigint NOT NULL,
  84 + "product_id" bigint NOT NULL,
  85 + "identifier" varchar(100) NOT NULL DEFAULT '',
230 86 "type" tinyint NOT NULL DEFAULT '1',
231 87 "data" text,
232 88 "ts" bigint NOT NULL DEFAULT '0',
233 89 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
234   - PRIMARY KEY
235   -(
236   - "id"
237   -)
238   - ) COMMENT 'IoT 设备数据表';
  90 + PRIMARY KEY ("id")
  91 +) COMMENT 'IoT 设备数据表';
239 92  
240   --- IoT 告警配置表
241   -CREATE TABLE IF NOT EXISTS "iot_alert_config"
242   -(
243   - "id"
244   - bigint
245   - NOT
246   - NULL
247   - GENERATED
248   - BY
249   - DEFAULT AS
250   - IDENTITY,
251   - "name"
252   - varchar
253   -(
254   - 255
255   -) NOT NULL DEFAULT '',
  93 +CREATE TABLE IF NOT EXISTS "iot_alert_config" (
  94 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  95 + "name" varchar(255) NOT NULL DEFAULT '',
256 96 "product_id" bigint NOT NULL,
257 97 "device_id" bigint DEFAULT NULL,
258 98 "rule_id" bigint DEFAULT NULL,
259 99 "status" tinyint NOT NULL DEFAULT '0',
260   - "creator" varchar
261   -(
262   - 64
263   -) DEFAULT '',
  100 + "creator" varchar(64) DEFAULT '',
264 101 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
265   - "updater" varchar
266   -(
267   - 64
268   -) DEFAULT '',
  102 + "updater" varchar(64) DEFAULT '',
269 103 "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
270 104 "deleted" bit NOT NULL DEFAULT FALSE,
271 105 "tenant_id" bigint NOT NULL DEFAULT '0',
272   - PRIMARY KEY
273   -(
274   - "id"
275   -)
276   - ) COMMENT 'IoT 告警配置表';
  106 + PRIMARY KEY ("id")
  107 +) COMMENT 'IoT 告警配置表';
277 108  
278   --- IoT 告警记录表
279   -CREATE TABLE IF NOT EXISTS "iot_alert_record"
280   -(
281   - "id"
282   - bigint
283   - NOT
284   - NULL
285   - GENERATED
286   - BY
287   - DEFAULT AS
288   - IDENTITY,
289   - "alert_config_id"
290   - bigint
291   - NOT
292   - NULL,
293   - "alert_name"
294   - varchar
295   -(
296   - 255
297   -) NOT NULL DEFAULT '',
  109 +CREATE TABLE IF NOT EXISTS "iot_alert_record" (
  110 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  111 + "alert_config_id" bigint NOT NULL,
  112 + "alert_name" varchar(255) NOT NULL DEFAULT '',
298 113 "product_id" bigint NOT NULL,
299 114 "device_id" bigint DEFAULT NULL,
300 115 "rule_id" bigint DEFAULT NULL,
... ... @@ -303,171 +118,65 @@ CREATE TABLE IF NOT EXISTS &quot;iot_alert_record&quot;
303 118 "deal_status" tinyint NOT NULL DEFAULT '0',
304 119 "deal_time" timestamp DEFAULT NULL,
305 120 "deal_user_id" bigint DEFAULT NULL,
306   - "deal_remark" varchar
307   -(
308   - 500
309   -) DEFAULT NULL,
310   - "creator" varchar
311   -(
312   - 64
313   -) DEFAULT '',
  121 + "deal_remark" varchar(500) DEFAULT NULL,
  122 + "creator" varchar(64) DEFAULT '',
314 123 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
315   - "updater" varchar
316   -(
317   - 64
318   -) DEFAULT '',
  124 + "updater" varchar(64) DEFAULT '',
319 125 "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
320 126 "deleted" bit NOT NULL DEFAULT FALSE,
321 127 "tenant_id" bigint NOT NULL DEFAULT '0',
322   - PRIMARY KEY
323   -(
324   - "id"
325   -)
326   - ) COMMENT 'IoT 告警记录表';
  128 + PRIMARY KEY ("id")
  129 +) COMMENT 'IoT 告警记录表';
327 130  
328   --- IoT OTA 固件表
329   -CREATE TABLE IF NOT EXISTS "iot_ota_firmware"
330   -(
331   - "id"
332   - bigint
333   - NOT
334   - NULL
335   - GENERATED
336   - BY
337   - DEFAULT AS
338   - IDENTITY,
339   - "name"
340   - varchar
341   -(
342   - 255
343   -) NOT NULL DEFAULT '',
  131 +CREATE TABLE IF NOT EXISTS "iot_ota_firmware" (
  132 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  133 + "name" varchar(255) NOT NULL DEFAULT '',
344 134 "product_id" bigint NOT NULL,
345   - "version" varchar
346   -(
347   - 50
348   -) NOT NULL DEFAULT '',
349   - "description" varchar
350   -(
351   - 500
352   -) DEFAULT NULL,
353   - "file_url" varchar
354   -(
355   - 500
356   -) DEFAULT NULL,
  135 + "version" varchar(50) NOT NULL DEFAULT '',
  136 + "description" varchar(500) DEFAULT NULL,
  137 + "file_url" varchar(500) DEFAULT NULL,
357 138 "file_size" bigint NOT NULL DEFAULT '0',
358 139 "status" tinyint NOT NULL DEFAULT '0',
359   - "creator" varchar
360   -(
361   - 64
362   -) DEFAULT '',
  140 + "creator" varchar(64) DEFAULT '',
363 141 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
364   - "updater" varchar
365   -(
366   - 64
367   -) DEFAULT '',
  142 + "updater" varchar(64) DEFAULT '',
368 143 "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
369 144 "deleted" bit NOT NULL DEFAULT FALSE,
370 145 "tenant_id" bigint NOT NULL DEFAULT '0',
371   - PRIMARY KEY
372   -(
373   - "id"
374   -)
375   - ) COMMENT 'IoT OTA 固件表';
  146 + PRIMARY KEY ("id")
  147 +) COMMENT 'IoT OTA 固件表';
376 148  
377   --- IoT OTA 升级任务表
378   -CREATE TABLE IF NOT EXISTS "iot_ota_task"
379   -(
380   - "id"
381   - bigint
382   - NOT
383   - NULL
384   - GENERATED
385   - BY
386   - DEFAULT AS
387   - IDENTITY,
388   - "name"
389   - varchar
390   -(
391   - 255
392   -) NOT NULL DEFAULT '',
  149 +CREATE TABLE IF NOT EXISTS "iot_ota_task" (
  150 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  151 + "name" varchar(255) NOT NULL DEFAULT '',
393 152 "firmware_id" bigint NOT NULL,
394 153 "product_id" bigint NOT NULL,
395 154 "upgrade_type" tinyint NOT NULL DEFAULT '0',
396 155 "status" tinyint NOT NULL DEFAULT '0',
397   - "creator" varchar
398   -(
399   - 64
400   -) DEFAULT '',
  156 + "creator" varchar(64) DEFAULT '',
401 157 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
402   - "updater" varchar
403   -(
404   - 64
405   -) DEFAULT '',
  158 + "updater" varchar(64) DEFAULT '',
406 159 "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
407 160 "deleted" bit NOT NULL DEFAULT FALSE,
408 161 "tenant_id" bigint NOT NULL DEFAULT '0',
409   - PRIMARY KEY
410   -(
411   - "id"
412   -)
413   - ) COMMENT 'IoT OTA 升级任务表';
  162 + PRIMARY KEY ("id")
  163 +) COMMENT 'IoT OTA 升级任务表';
414 164  
415   --- IoT OTA 升级记录表
416   -CREATE TABLE IF NOT EXISTS "iot_ota_record"
417   -(
418   - "id"
419   - bigint
420   - NOT
421   - NULL
422   - GENERATED
423   - BY
424   - DEFAULT AS
425   - IDENTITY,
426   - "task_id"
427   - bigint
428   - NOT
429   - NULL,
430   - "firmware_id"
431   - bigint
432   - NOT
433   - NULL,
434   - "device_id"
435   - bigint
436   - NOT
437   - NULL,
438   - "status"
439   - tinyint
440   - NOT
441   - NULL
442   - DEFAULT
443   - '0',
444   - "progress"
445   - int
446   - NOT
447   - NULL
448   - DEFAULT
449   - '0',
450   - "error_msg"
451   - varchar
452   -(
453   - 500
454   -) DEFAULT NULL,
  165 +CREATE TABLE IF NOT EXISTS "iot_ota_record" (
  166 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  167 + "task_id" bigint NOT NULL,
  168 + "firmware_id" bigint NOT NULL,
  169 + "device_id" bigint NOT NULL,
  170 + "status" tinyint NOT NULL DEFAULT '0',
  171 + "progress" int NOT NULL DEFAULT '0',
  172 + "error_msg" varchar(500) DEFAULT NULL,
455 173 "start_time" timestamp DEFAULT NULL,
456 174 "end_time" timestamp DEFAULT NULL,
457   - "creator" varchar
458   -(
459   - 64
460   -) DEFAULT '',
  175 + "creator" varchar(64) DEFAULT '',
461 176 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
462   - "updater" varchar
463   -(
464   - 64
465   -) DEFAULT '',
  177 + "updater" varchar(64) DEFAULT '',
466 178 "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
467 179 "deleted" bit NOT NULL DEFAULT FALSE,
468 180 "tenant_id" bigint NOT NULL DEFAULT '0',
469   - PRIMARY KEY
470   -(
471   - "id"
472   -)
473   - ) COMMENT 'IoT OTA 升级记录表';
  181 + PRIMARY KEY ("id")
  182 +) COMMENT 'IoT OTA 升级记录表';
... ...