!1403 perf:【IoT 物联网】场景规则匹配优化
Merge pull request !1403 from puhui999/feature/iot
This commit is contained in:
commit
f88d30f103
@ -132,7 +132,7 @@ public class IotDeviceController {
|
||||
List<IotDeviceDO> list = deviceService.getDeviceListByCondition(deviceType, productId);
|
||||
return success(convertList(list, device -> // 只返回 id、name、productId 字段
|
||||
new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName())
|
||||
.setProductId(device.getProductId())));
|
||||
.setProductId(device.getProductId()).setState(device.getState())));
|
||||
}
|
||||
|
||||
@PostMapping("/import")
|
||||
|
||||
@ -4,12 +4,12 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleScenePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneUpdateStatusReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleUpdateStatusReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.IotRuleSceneService;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@ -24,71 +24,70 @@ import java.util.List;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
// TODO @puhui999:SceneRule 方法名,类名等;
|
||||
@Tag(name = "管理后台 - IoT 场景联动")
|
||||
@RestController
|
||||
@RequestMapping("/iot/rule-scene")
|
||||
@RequestMapping("/iot/scene-rule")
|
||||
@Validated
|
||||
public class IotRuleSceneController {
|
||||
public class IotSceneRuleController {
|
||||
|
||||
@Resource
|
||||
private IotRuleSceneService ruleSceneService;
|
||||
private IotSceneRuleService sceneRuleService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建场景联动")
|
||||
@PreAuthorize("@ss.hasPermission('iot:rule-scene:create')")
|
||||
public CommonResult<Long> createRuleScene(@Valid @RequestBody IotRuleSceneSaveReqVO createReqVO) {
|
||||
return success(ruleSceneService.createRuleScene(createReqVO));
|
||||
@PreAuthorize("@ss.hasPermission('iot:scene-rule:create')")
|
||||
public CommonResult<Long> createSceneRule(@Valid @RequestBody IotSceneRuleSaveReqVO createReqVO) {
|
||||
return success(sceneRuleService.createSceneRule(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新场景联动")
|
||||
@PreAuthorize("@ss.hasPermission('iot:rule-scene:update')")
|
||||
public CommonResult<Boolean> updateRuleScene(@Valid @RequestBody IotRuleSceneSaveReqVO updateReqVO) {
|
||||
ruleSceneService.updateRuleScene(updateReqVO);
|
||||
@PreAuthorize("@ss.hasPermission('iot:scene-rule:update')")
|
||||
public CommonResult<Boolean> updateSceneRule(@Valid @RequestBody IotSceneRuleSaveReqVO updateReqVO) {
|
||||
sceneRuleService.updateSceneRule(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/update-status")
|
||||
@Operation(summary = "更新场景联动状态")
|
||||
@PreAuthorize("@ss.hasPermission('iot:rule-scene:update')")
|
||||
public CommonResult<Boolean> updateRuleSceneStatus(@Valid @RequestBody IotRuleSceneUpdateStatusReqVO updateReqVO) {
|
||||
ruleSceneService.updateRuleSceneStatus(updateReqVO.getId(), updateReqVO.getStatus());
|
||||
@PreAuthorize("@ss.hasPermission('iot:scene-rule:update')")
|
||||
public CommonResult<Boolean> updateSceneRuleStatus(@Valid @RequestBody IotSceneRuleUpdateStatusReqVO updateReqVO) {
|
||||
sceneRuleService.updateSceneRuleStatus(updateReqVO.getId(), updateReqVO.getStatus());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除场景联动")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('iot:rule-scene:delete')")
|
||||
public CommonResult<Boolean> deleteRuleScene(@RequestParam("id") Long id) {
|
||||
ruleSceneService.deleteRuleScene(id);
|
||||
@PreAuthorize("@ss.hasPermission('iot:scene-rule:delete')")
|
||||
public CommonResult<Boolean> deleteSceneRule(@RequestParam("id") Long id) {
|
||||
sceneRuleService.deleteSceneRule(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得场景联动")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('iot:rule-scene:query')")
|
||||
public CommonResult<IotRuleSceneRespVO> getRuleScene(@RequestParam("id") Long id) {
|
||||
IotSceneRuleDO ruleScene = ruleSceneService.getRuleScene(id);
|
||||
return success(BeanUtils.toBean(ruleScene, IotRuleSceneRespVO.class));
|
||||
@PreAuthorize("@ss.hasPermission('iot:scene-rule:query')")
|
||||
public CommonResult<IotSceneRuleRespVO> getSceneRule(@RequestParam("id") Long id) {
|
||||
IotSceneRuleDO sceneRule = sceneRuleService.getSceneRule(id);
|
||||
return success(BeanUtils.toBean(sceneRule, IotSceneRuleRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得场景联动分页")
|
||||
@PreAuthorize("@ss.hasPermission('iot:rule-scene:query')")
|
||||
public CommonResult<PageResult<IotRuleSceneRespVO>> getRuleScenePage(@Valid IotRuleScenePageReqVO pageReqVO) {
|
||||
PageResult<IotSceneRuleDO> pageResult = ruleSceneService.getRuleScenePage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, IotRuleSceneRespVO.class));
|
||||
@PreAuthorize("@ss.hasPermission('iot:scene-rule:query')")
|
||||
public CommonResult<PageResult<IotSceneRuleRespVO>> getSceneRulePage(@Valid IotSceneRulePageReqVO pageReqVO) {
|
||||
PageResult<IotSceneRuleDO> pageResult = sceneRuleService.getSceneRulePage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, IotSceneRuleRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/simple-list")
|
||||
@Operation(summary = "获取场景联动的精简信息列表", description = "主要用于前端的下拉选项")
|
||||
public CommonResult<List<IotRuleSceneRespVO>> getRuleSceneSimpleList() {
|
||||
List<IotSceneRuleDO> list = ruleSceneService.getRuleSceneListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
public CommonResult<List<IotSceneRuleRespVO>> getSceneRuleSimpleList() {
|
||||
List<IotSceneRuleDO> list = sceneRuleService.getSceneRuleListByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
return success(convertList(list, scene -> // 只返回 id、name 字段
|
||||
new IotRuleSceneRespVO().setId(scene.getId()).setName(scene.getName())));
|
||||
new IotSceneRuleRespVO().setId(scene.getId()).setName(scene.getName())));
|
||||
}
|
||||
|
||||
}
|
||||
@ -17,7 +17,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class IotRuleScenePageReqVO extends PageParam {
|
||||
public class IotSceneRulePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "场景名称", example = "赵六")
|
||||
private String name;
|
||||
@ -9,7 +9,7 @@ import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 场景联动 Response VO")
|
||||
@Data
|
||||
public class IotRuleSceneRespVO {
|
||||
public class IotSceneRuleRespVO {
|
||||
|
||||
@Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865")
|
||||
private Long id;
|
||||
@ -12,7 +12,7 @@ import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 场景联动新增/修改 Request VO")
|
||||
@Data
|
||||
public class IotRuleSceneSaveReqVO {
|
||||
public class IotSceneRuleSaveReqVO {
|
||||
|
||||
@Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865")
|
||||
private Long id;
|
||||
@ -8,7 +8,7 @@ import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 场景联动更新状态 Request VO")
|
||||
@Data
|
||||
public class IotRuleSceneUpdateStatusReqVO {
|
||||
public class IotSceneRuleUpdateStatusReqVO {
|
||||
|
||||
@Schema(description = "场景联动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "场景联动编号不能为空")
|
||||
@ -7,10 +7,10 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneConditionOperatorEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@ -79,13 +79,13 @@ public class IotSceneRuleDO extends TenantBaseDO {
|
||||
/**
|
||||
* 场景事件类型
|
||||
*
|
||||
* 枚举 {@link IotRuleSceneTriggerTypeEnum}
|
||||
* 1. {@link IotRuleSceneTriggerTypeEnum#DEVICE_STATE_UPDATE} 时,operator 非空,并且 value 为在线状态
|
||||
* 2. {@link IotRuleSceneTriggerTypeEnum#DEVICE_PROPERTY_POST}
|
||||
* {@link IotRuleSceneTriggerTypeEnum#DEVICE_EVENT_POST} 时,identifier、operator 非空,并且 value 为属性值
|
||||
* 3. {@link IotRuleSceneTriggerTypeEnum#DEVICE_EVENT_POST}
|
||||
* {@link IotRuleSceneTriggerTypeEnum#DEVICE_SERVICE_INVOKE} 时,identifier 非空,但是 operator、value 为空
|
||||
* 4. {@link IotRuleSceneTriggerTypeEnum#TIMER} 时,conditions 非空,并且设备无关(无需 productId、deviceId 字段)
|
||||
* 枚举 {@link IotSceneRuleTriggerTypeEnum}
|
||||
* 1. {@link IotSceneRuleTriggerTypeEnum#DEVICE_STATE_UPDATE} 时,operator 非空,并且 value 为在线状态
|
||||
* 2. {@link IotSceneRuleTriggerTypeEnum#DEVICE_PROPERTY_POST}
|
||||
* {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST} 时,identifier、operator 非空,并且 value 为属性值
|
||||
* 3. {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST}
|
||||
* {@link IotSceneRuleTriggerTypeEnum#DEVICE_SERVICE_INVOKE} 时,identifier 非空,但是 operator、value 为空
|
||||
* 4. {@link IotSceneRuleTriggerTypeEnum#TIMER} 时,conditions 非空,并且设备无关(无需 productId、deviceId 字段)
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
@ -111,14 +111,14 @@ public class IotSceneRuleDO extends TenantBaseDO {
|
||||
/**
|
||||
* 操作符
|
||||
*
|
||||
* 枚举 {@link IotRuleSceneConditionOperatorEnum}
|
||||
* 枚举 {@link IotSceneRuleConditionOperatorEnum}
|
||||
*/
|
||||
private String operator;
|
||||
/**
|
||||
* 参数(属性值、在线状态)
|
||||
* <p>
|
||||
* 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。
|
||||
* 例如说,{@link IotRuleSceneConditionOperatorEnum#IN}、{@link IotRuleSceneConditionOperatorEnum#BETWEEN}
|
||||
* 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN}
|
||||
*/
|
||||
private String value;
|
||||
|
||||
@ -148,10 +148,10 @@ public class IotSceneRuleDO extends TenantBaseDO {
|
||||
/**
|
||||
* 触发条件类型
|
||||
*
|
||||
* 枚举 {@link IotRuleSceneConditionTypeEnum}
|
||||
* 1. {@link IotRuleSceneConditionTypeEnum#DEVICE_STATE} 时,operator 非空,并且 value 为在线状态
|
||||
* 2. {@link IotRuleSceneConditionTypeEnum#DEVICE_PROPERTY} 时,identifier、operator 非空,并且 value 为属性值
|
||||
* 3. {@link IotRuleSceneConditionTypeEnum#CURRENT_TIME} 时,operator 非空(使用 DATE_TIME_ 和 TIME_ 部分),并且 value 非空
|
||||
* 枚举 {@link IotSceneRuleConditionTypeEnum}
|
||||
* 1. {@link IotSceneRuleConditionTypeEnum#DEVICE_STATE} 时,operator 非空,并且 value 为在线状态
|
||||
* 2. {@link IotSceneRuleConditionTypeEnum#DEVICE_PROPERTY} 时,identifier、operator 非空,并且 value 为属性值
|
||||
* 3. {@link IotSceneRuleConditionTypeEnum#CURRENT_TIME} 时,operator 非空(使用 DATE_TIME_ 和 TIME_ 部分),并且 value 非空
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
@ -176,14 +176,14 @@ public class IotSceneRuleDO extends TenantBaseDO {
|
||||
/**
|
||||
* 操作符
|
||||
*
|
||||
* 枚举 {@link IotRuleSceneConditionOperatorEnum}
|
||||
* 枚举 {@link IotSceneRuleConditionOperatorEnum}
|
||||
*/
|
||||
private String operator;
|
||||
/**
|
||||
* 参数
|
||||
*
|
||||
* 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。
|
||||
* 例如说,{@link IotRuleSceneConditionOperatorEnum#IN}、{@link IotRuleSceneConditionOperatorEnum#BETWEEN}
|
||||
* 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN}
|
||||
*/
|
||||
private String param;
|
||||
|
||||
@ -198,11 +198,11 @@ public class IotSceneRuleDO extends TenantBaseDO {
|
||||
/**
|
||||
* 执行类型
|
||||
*
|
||||
* 枚举 {@link IotRuleSceneActionTypeEnum}
|
||||
* 1. {@link IotRuleSceneActionTypeEnum#DEVICE_PROPERTY_SET} 时,params 非空
|
||||
* {@link IotRuleSceneActionTypeEnum#DEVICE_SERVICE_INVOKE} 时,params 非空
|
||||
* 2. {@link IotRuleSceneActionTypeEnum#ALERT_TRIGGER} 时,alertConfigId 为空,因为是 {@link IotAlertConfigDO} 里面关联它
|
||||
* 3. {@link IotRuleSceneActionTypeEnum#ALERT_RECOVER} 时,alertConfigId 非空
|
||||
* 枚举 {@link IotSceneRuleActionTypeEnum}
|
||||
* 1. {@link IotSceneRuleActionTypeEnum#DEVICE_PROPERTY_SET} 时,params 非空
|
||||
* {@link IotSceneRuleActionTypeEnum#DEVICE_SERVICE_INVOKE} 时,params 非空
|
||||
* 2. {@link IotSceneRuleActionTypeEnum#ALERT_TRIGGER} 时,alertConfigId 为空,因为是 {@link IotAlertConfigDO} 里面关联它
|
||||
* 3. {@link IotSceneRuleActionTypeEnum#ALERT_RECOVER} 时,alertConfigId 非空
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ import lombok.Data;
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = IotDataSinkHttpConfig.class, name = "1"),
|
||||
@JsonSubTypes.Type(value = IotDataSinkMqttConfig.class, name = "10"),
|
||||
@JsonSubTypes.Type(value = IotDataSinkRedisStreamConfig.class, name = "21"),
|
||||
@JsonSubTypes.Type(value = IotDataSinkRedisConfig.class, name = "21"),
|
||||
@JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"),
|
||||
@JsonSubTypes.Type(value = IotDataSinkRabbitMQConfig.class, name = "31"),
|
||||
@JsonSubTypes.Type(value = IotDataSinkKafkaConfig.class, name = "32"),
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRedisDataStructureEnum;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* IoT Redis 配置 {@link IotAbstractDataSinkConfig} 实现类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Data
|
||||
public class IotDataSinkRedisConfig extends IotAbstractDataSinkConfig {
|
||||
|
||||
/**
|
||||
* Redis 服务器地址
|
||||
*/
|
||||
private String host;
|
||||
/**
|
||||
* 端口
|
||||
*/
|
||||
private Integer port;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
/**
|
||||
* 数据库索引
|
||||
*/
|
||||
private Integer database;
|
||||
|
||||
/**
|
||||
* Redis 数据结构类型
|
||||
* <p>
|
||||
* 枚举 {@link IotRedisDataStructureEnum}
|
||||
*/
|
||||
@InEnum(IotRedisDataStructureEnum.class)
|
||||
private Integer dataStructure;
|
||||
|
||||
/**
|
||||
* 主题/键名
|
||||
* <p>
|
||||
* 对于不同的数据结构:
|
||||
* - Stream: 流的键名
|
||||
* - Hash: Hash 的键名
|
||||
* - List: 列表的键名
|
||||
* - Set: 集合的键名
|
||||
* - ZSet: 有序集合的键名
|
||||
* - String: 字符串的键名
|
||||
*/
|
||||
private String topic;
|
||||
|
||||
/**
|
||||
* Hash 字段名(仅当 dataStructure 为 HASH 时使用)
|
||||
*/
|
||||
private String hashField;
|
||||
|
||||
/**
|
||||
* ZSet 分数字段(仅当 dataStructure 为 ZSET 时使用)
|
||||
* 指定消息中哪个字段作为分数,如果不指定则使用当前时间戳
|
||||
*/
|
||||
private String scoreField;
|
||||
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* IoT Redis Stream 配置 {@link IotAbstractDataSinkConfig} 实现类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Data
|
||||
public class IotDataSinkRedisStreamConfig extends IotAbstractDataSinkConfig {
|
||||
|
||||
/**
|
||||
* Redis 服务器地址
|
||||
*/
|
||||
private String host;
|
||||
/**
|
||||
* 端口
|
||||
*/
|
||||
private Integer port;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
/**
|
||||
* 数据库索引
|
||||
*/
|
||||
private Integer database;
|
||||
|
||||
/**
|
||||
* 主题
|
||||
*/
|
||||
private String topic;
|
||||
}
|
||||
@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.dal.mysql.rule;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleScenePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@ -15,9 +15,9 @@ import java.util.List;
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Mapper
|
||||
public interface IotRuleSceneMapper extends BaseMapperX<IotSceneRuleDO> {
|
||||
public interface IotSceneRuleMapper extends BaseMapperX<IotSceneRuleDO> {
|
||||
|
||||
default PageResult<IotSceneRuleDO> selectPage(IotRuleScenePageReqVO reqVO) {
|
||||
default PageResult<IotSceneRuleDO> selectPage(IotSceneRulePageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<IotSceneRuleDO>()
|
||||
.likeIfPresent(IotSceneRuleDO::getName, reqVO.getName())
|
||||
.likeIfPresent(IotSceneRuleDO::getDescription, reqVO.getDescription())
|
||||
@ -22,7 +22,7 @@ public enum IotDataSinkTypeEnum implements ArrayValuable<Integer> {
|
||||
MQTT(10, "MQTT"), // TODO 待实现;
|
||||
|
||||
DATABASE(20, "Database"), // TODO @puhui999:待实现;可以简单点,对应的表名是什么,字段先固定了。
|
||||
REDIS_STREAM(21, "Redis Stream"), // TODO @puhui999:改成 Redis;然后枚举不同的数据结构?这样,枚举就可以是 Redis 了
|
||||
REDIS(21, "Redis"),
|
||||
|
||||
ROCKETMQ(30, "RocketMQ"),
|
||||
RABBITMQ(31, "RabbitMQ"),
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
package cn.iocoder.yudao.module.iot.enums.rule;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* IoT Redis 数据结构类型枚举
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum IotRedisDataStructureEnum implements ArrayValuable<Integer> {
|
||||
|
||||
STREAM(1, "Stream"),
|
||||
HASH(2, "Hash"),
|
||||
LIST(3, "List"),
|
||||
SET(4, "Set"),
|
||||
ZSET(5, "ZSet"),
|
||||
STRING(6, "String");
|
||||
|
||||
private final Integer type;
|
||||
|
||||
private final String name;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRedisDataStructureEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
}
|
||||
@ -14,7 +14,7 @@ import java.util.Arrays;
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum IotRuleSceneActionTypeEnum implements ArrayValuable<Integer> {
|
||||
public enum IotSceneRuleActionTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
/**
|
||||
* 设备属性设置
|
||||
@ -42,7 +42,7 @@ public enum IotRuleSceneActionTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
private final Integer type;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneActionTypeEnum::getType).toArray(Integer[]::new);
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleActionTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
@ -14,7 +14,7 @@ import java.util.Arrays;
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum IotRuleSceneConditionOperatorEnum implements ArrayValuable<String> {
|
||||
public enum IotSceneRuleConditionOperatorEnum implements ArrayValuable<String> {
|
||||
|
||||
EQUALS("=", "#source == #value"),
|
||||
NOT_EQUALS("!=", "!(#source == #value)"),
|
||||
@ -53,7 +53,7 @@ public enum IotRuleSceneConditionOperatorEnum implements ArrayValuable<String> {
|
||||
private final String operator;
|
||||
private final String springExpression;
|
||||
|
||||
public static final String[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneConditionOperatorEnum::getOperator).toArray(String[]::new);
|
||||
public static final String[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleConditionOperatorEnum::getOperator).toArray(String[]::new);
|
||||
|
||||
/**
|
||||
* Spring 表达式 - 原始值
|
||||
@ -68,7 +68,7 @@ public enum IotRuleSceneConditionOperatorEnum implements ArrayValuable<String> {
|
||||
*/
|
||||
public static final String SPRING_EXPRESSION_VALUE_LIST = "values";
|
||||
|
||||
public static IotRuleSceneConditionOperatorEnum operatorOf(String operator) {
|
||||
public static IotSceneRuleConditionOperatorEnum operatorOf(String operator) {
|
||||
return ArrayUtil.firstMatch(item -> item.getOperator().equals(operator), values());
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import java.util.Arrays;
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum IotRuleSceneConditionTypeEnum implements ArrayValuable<Integer> {
|
||||
public enum IotSceneRuleConditionTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
DEVICE_STATE(1, "设备状态"),
|
||||
DEVICE_PROPERTY(2, "设备属性"),
|
||||
@ -25,7 +25,7 @@ public enum IotRuleSceneConditionTypeEnum implements ArrayValuable<Integer> {
|
||||
private final Integer type;
|
||||
private final String name;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneConditionTypeEnum::getType).toArray(Integer[]::new);
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleConditionTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
@ -16,10 +16,7 @@ import java.util.Arrays;
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum IotRuleSceneTriggerTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
@Deprecated
|
||||
DEVICE(1), // 设备触发 // TODO @puhui999:@芋艿:这个可以作废
|
||||
public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
// TODO @芋艿:后续“对应”部分,要 @下,等包结构梳理完;
|
||||
/**
|
||||
@ -56,11 +53,25 @@ public enum IotRuleSceneTriggerTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
private final Integer type;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneTriggerTypeEnum::getType).toArray(Integer[]::new);
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotSceneRuleTriggerTypeEnum::getType).toArray(Integer[]::new);
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据类型值查找触发器类型枚举
|
||||
*
|
||||
* @param typeValue 类型值
|
||||
* @return 触发器类型枚举
|
||||
*/
|
||||
public static IotSceneRuleTriggerTypeEnum findTriggerTypeEnum(Integer typeValue) {
|
||||
return Arrays.stream(IotSceneRuleTriggerTypeEnum.values())
|
||||
.filter(type -> type.getType().equals(typeValue))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.job.rule;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.IotRuleSceneService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.springframework.scheduling.quartz.QuartzJobBean;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IoT 规则场景 Job,用于执行 {@link IotRuleSceneTriggerTypeEnum#TIMER} 类型的规则场景
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class IotRuleSceneJob extends QuartzJobBean {
|
||||
|
||||
/**
|
||||
* JobData Key - 规则场景编号
|
||||
*/
|
||||
public static final String JOB_DATA_KEY_RULE_SCENE_ID = "ruleSceneId";
|
||||
|
||||
@Resource
|
||||
private IotRuleSceneService ruleSceneService;
|
||||
|
||||
@Override
|
||||
protected void executeInternal(JobExecutionContext context) {
|
||||
// 获得规则场景编号
|
||||
Long ruleSceneId = context.getMergedJobDataMap().getLong(JOB_DATA_KEY_RULE_SCENE_ID);
|
||||
|
||||
// 执行规则场景
|
||||
ruleSceneService.executeRuleSceneByTimer(ruleSceneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 JobData Map
|
||||
*
|
||||
* @param ruleSceneId 规则场景编号
|
||||
* @return JobData Map
|
||||
*/
|
||||
public static Map<String, Object> buildJobDataMap(Long ruleSceneId) {
|
||||
return MapUtil.of(JOB_DATA_KEY_RULE_SCENE_ID, ruleSceneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Job 名字
|
||||
*
|
||||
* @param ruleSceneId 规则场景编号
|
||||
* @return Job 名字
|
||||
*/
|
||||
public static String buildJobName(Long ruleSceneId) {
|
||||
return String.format("%s_%d", IotRuleSceneJob.class.getSimpleName(), ruleSceneId);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package cn.iocoder.yudao.module.iot.job.rule;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.springframework.scheduling.quartz.QuartzJobBean;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IoT 规则场景 Job,用于执行 {@link IotSceneRuleTriggerTypeEnum#TIMER} 类型的规则场景
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class IotSceneRuleJob extends QuartzJobBean {
|
||||
|
||||
/**
|
||||
* JobData Key - 规则场景编号
|
||||
*/
|
||||
public static final String JOB_DATA_KEY_RULE_SCENE_ID = "sceneRuleId";
|
||||
|
||||
@Resource
|
||||
private IotSceneRuleService sceneRuleService;
|
||||
|
||||
@Override
|
||||
protected void executeInternal(JobExecutionContext context) {
|
||||
// 获得规则场景编号
|
||||
Long sceneRuleId = context.getMergedJobDataMap().getLong(JOB_DATA_KEY_RULE_SCENE_ID);
|
||||
|
||||
// 执行规则场景
|
||||
sceneRuleService.executeSceneRuleByTimer(sceneRuleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 JobData Map
|
||||
*
|
||||
* @param sceneRuleId 规则场景编号
|
||||
* @return JobData Map
|
||||
*/
|
||||
public static Map<String, Object> buildJobDataMap(Long sceneRuleId) {
|
||||
return MapUtil.of(JOB_DATA_KEY_RULE_SCENE_ID, sceneRuleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Job 名字
|
||||
*
|
||||
* @param sceneRuleId 规则场景编号
|
||||
* @return Job 名字
|
||||
*/
|
||||
public static String buildJobName(Long sceneRuleId) {
|
||||
return String.format("%s_%d", IotSceneRuleJob.class.getSimpleName(), sceneRuleId);
|
||||
}
|
||||
|
||||
}
|
||||
@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.mq.consumer.rule;
|
||||
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
|
||||
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.IotRuleSceneService;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -17,10 +17,10 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IotRuleSceneMessageHandler implements IotMessageSubscriber<IotDeviceMessage> {
|
||||
public class IotSceneRuleMessageHandler implements IotMessageSubscriber<IotDeviceMessage> {
|
||||
|
||||
@Resource
|
||||
private IotRuleSceneService ruleSceneService;
|
||||
private IotSceneRuleService sceneRuleService;
|
||||
|
||||
@Resource
|
||||
private IotMessageBus messageBus;
|
||||
@ -46,7 +46,7 @@ public class IotRuleSceneMessageHandler implements IotMessageSubscriber<IotDevic
|
||||
return;
|
||||
}
|
||||
log.info("[onMessage][消息内容({})]", message);
|
||||
ruleSceneService.executeRuleSceneByDevice(message);
|
||||
sceneRuleService.executeSceneRuleByDevice(message);
|
||||
}
|
||||
|
||||
}
|
||||
@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConf
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.alert.IotAlertConfigMapper;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.IotRuleSceneService;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
@ -32,7 +32,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService {
|
||||
|
||||
@Resource
|
||||
@Lazy // 延迟,避免循环依赖报错
|
||||
private IotRuleSceneService ruleSceneService;
|
||||
private IotSceneRuleService sceneRuleService;
|
||||
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
@ -40,7 +40,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService {
|
||||
@Override
|
||||
public Long createAlertConfig(IotAlertConfigSaveReqVO createReqVO) {
|
||||
// 校验关联数据是否存在
|
||||
ruleSceneService.validateRuleSceneList(createReqVO.getSceneRuleIds());
|
||||
sceneRuleService.validateSceneRuleList(createReqVO.getSceneRuleIds());
|
||||
adminUserApi.validateUserList(createReqVO.getReceiveUserIds());
|
||||
|
||||
// 插入
|
||||
@ -54,7 +54,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService {
|
||||
// 校验存在
|
||||
validateAlertConfigExists(updateReqVO.getId());
|
||||
// 校验关联数据是否存在
|
||||
ruleSceneService.validateRuleSceneList(updateReqVO.getSceneRuleIds());
|
||||
sceneRuleService.validateSceneRuleList(updateReqVO.getSceneRuleIds());
|
||||
adminUserApi.validateUserList(updateReqVO.getReceiveUserIds());
|
||||
|
||||
// 更新
|
||||
|
||||
@ -0,0 +1,182 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.data.action;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkRedisConfig;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRedisDataStructureEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.config.Config;
|
||||
import org.redisson.config.SingleServerConfig;
|
||||
import org.redisson.spring.data.connection.RedissonConnectionFactory;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.stream.ObjectRecord;
|
||||
import org.springframework.data.redis.connection.stream.StreamRecords;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Redis 的 {@link IotDataRuleAction} 实现类
|
||||
* 支持多种 Redis 数据结构:Stream、Hash、List、Set、ZSet、String
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IotRedisRuleAction extends
|
||||
IotDataRuleCacheableAction<IotDataSinkRedisConfig, RedisTemplate<String, Object>> {
|
||||
|
||||
@Override
|
||||
public Integer getType() {
|
||||
return IotDataSinkTypeEnum.REDIS.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(IotDeviceMessage message, IotDataSinkRedisConfig config) throws Exception {
|
||||
// 1. 获取 RedisTemplate
|
||||
RedisTemplate<String, Object> redisTemplate = getProducer(config);
|
||||
|
||||
// 2. 根据数据结构类型执行不同的操作
|
||||
String messageJson = JsonUtils.toJsonString(message);
|
||||
IotRedisDataStructureEnum dataStructure = getDataStructureByType(config.getDataStructure());
|
||||
|
||||
switch (dataStructure) {
|
||||
case STREAM:
|
||||
executeStream(redisTemplate, config, messageJson);
|
||||
break;
|
||||
case HASH:
|
||||
executeHash(redisTemplate, config, message, messageJson);
|
||||
break;
|
||||
case LIST:
|
||||
executeList(redisTemplate, config, messageJson);
|
||||
break;
|
||||
case SET:
|
||||
executeSet(redisTemplate, config, messageJson);
|
||||
break;
|
||||
case ZSET:
|
||||
executeZSet(redisTemplate, config, message, messageJson);
|
||||
break;
|
||||
case STRING:
|
||||
executeString(redisTemplate, config, messageJson);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("不支持的 Redis 数据结构类型: " + dataStructure);
|
||||
}
|
||||
|
||||
log.info("[execute][消息发送成功] dataStructure: {}, config: {}", dataStructure.getName(), config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 Stream 操作
|
||||
*/
|
||||
private void executeStream(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config, String messageJson) {
|
||||
ObjectRecord<String, ?> record = StreamRecords.newRecord()
|
||||
.ofObject(messageJson).withStreamKey(config.getTopic());
|
||||
redisTemplate.opsForStream().add(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 Hash 操作
|
||||
*/
|
||||
private void executeHash(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config,
|
||||
IotDeviceMessage message, String messageJson) {
|
||||
String hashField = StrUtil.isNotBlank(config.getHashField()) ?
|
||||
config.getHashField() : String.valueOf(message.getDeviceId());
|
||||
redisTemplate.opsForHash().put(config.getTopic(), hashField, messageJson);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 List 操作
|
||||
*/
|
||||
private void executeList(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config, String messageJson) {
|
||||
redisTemplate.opsForList().rightPush(config.getTopic(), messageJson);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 Set 操作
|
||||
*/
|
||||
private void executeSet(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config, String messageJson) {
|
||||
redisTemplate.opsForSet().add(config.getTopic(), messageJson);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 ZSet 操作
|
||||
*/
|
||||
private void executeZSet(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config,
|
||||
IotDeviceMessage message, String messageJson) {
|
||||
double score;
|
||||
if (StrUtil.isNotBlank(config.getScoreField())) {
|
||||
// 尝试从消息中获取分数字段
|
||||
try {
|
||||
Map<String, Object> messageMap = JsonUtils.parseObject(messageJson, Map.class);
|
||||
Object scoreValue = messageMap.get(config.getScoreField());
|
||||
score = scoreValue instanceof Number ? ((Number) scoreValue).doubleValue() : System.currentTimeMillis();
|
||||
} catch (Exception e) {
|
||||
score = System.currentTimeMillis();
|
||||
}
|
||||
} else {
|
||||
// 使用当前时间戳作为分数
|
||||
score = System.currentTimeMillis();
|
||||
}
|
||||
redisTemplate.opsForZSet().add(config.getTopic(), messageJson, score);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 String 操作
|
||||
*/
|
||||
private void executeString(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config, String messageJson) {
|
||||
redisTemplate.opsForValue().set(config.getTopic(), messageJson);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RedisTemplate<String, Object> initProducer(IotDataSinkRedisConfig config) {
|
||||
// 1.1 创建 Redisson 配置
|
||||
Config redissonConfig = new Config();
|
||||
SingleServerConfig serverConfig = redissonConfig.useSingleServer()
|
||||
.setAddress("redis://" + config.getHost() + ":" + config.getPort())
|
||||
.setDatabase(config.getDatabase());
|
||||
// 1.2 设置密码(如果有)
|
||||
if (StrUtil.isNotBlank(config.getPassword())) {
|
||||
serverConfig.setPassword(config.getPassword());
|
||||
}
|
||||
|
||||
// 2.1 创建 RedisTemplate 并配置
|
||||
RedissonClient redisson = Redisson.create(redissonConfig);
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(new RedissonConnectionFactory(redisson));
|
||||
// 2.2 设置序列化器
|
||||
template.setKeySerializer(RedisSerializer.string());
|
||||
template.setHashKeySerializer(RedisSerializer.string());
|
||||
template.setValueSerializer(RedisSerializer.json());
|
||||
template.setHashValueSerializer(RedisSerializer.json());
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void closeProducer(RedisTemplate<String, Object> producer) throws Exception {
|
||||
RedisConnectionFactory factory = producer.getConnectionFactory();
|
||||
if (factory != null) {
|
||||
((RedissonConnectionFactory) factory).destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类型值获取数据结构枚举
|
||||
*/
|
||||
private IotRedisDataStructureEnum getDataStructureByType(Integer type) {
|
||||
for (IotRedisDataStructureEnum dataStructure : IotRedisDataStructureEnum.values()) {
|
||||
if (dataStructure.getType().equals(type)) {
|
||||
return dataStructure;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("不支持的 Redis 数据结构类型: " + type);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.data.action;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkRedisStreamConfig;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.config.Config;
|
||||
import org.redisson.config.SingleServerConfig;
|
||||
import org.redisson.spring.data.connection.RedissonConnectionFactory;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.stream.ObjectRecord;
|
||||
import org.springframework.data.redis.connection.stream.StreamRecords;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Redis Stream 的 {@link IotDataRuleAction} 实现类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IotRedisStreamRuleAction extends
|
||||
IotDataRuleCacheableAction<IotDataSinkRedisStreamConfig, RedisTemplate<String, Object>> {
|
||||
|
||||
@Override
|
||||
public Integer getType() {
|
||||
return IotDataSinkTypeEnum.REDIS_STREAM.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(IotDeviceMessage message, IotDataSinkRedisStreamConfig config) throws Exception {
|
||||
// 1. 获取 RedisTemplate
|
||||
RedisTemplate<String, Object> redisTemplate = getProducer(config);
|
||||
|
||||
// 2. 创建并发送 Stream 记录
|
||||
ObjectRecord<String, ?> record = StreamRecords.newRecord()
|
||||
.ofObject(JsonUtils.toJsonString(message)).withStreamKey(config.getTopic());
|
||||
String recordId = String.valueOf(redisTemplate.opsForStream().add(record));
|
||||
log.info("[execute][消息发送成功] messageId: {}, config: {}", recordId, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RedisTemplate<String, Object> initProducer(IotDataSinkRedisStreamConfig config) {
|
||||
// 1.1 创建 Redisson 配置
|
||||
Config redissonConfig = new Config();
|
||||
SingleServerConfig serverConfig = redissonConfig.useSingleServer()
|
||||
.setAddress("redis://" + config.getHost() + ":" + config.getPort())
|
||||
.setDatabase(config.getDatabase());
|
||||
// 1.2 设置密码(如果有)
|
||||
if (StrUtil.isNotBlank(config.getPassword())) {
|
||||
serverConfig.setPassword(config.getPassword());
|
||||
}
|
||||
|
||||
// 2.1 创建 RedisTemplate 并配置
|
||||
RedissonClient redisson = Redisson.create(redissonConfig);
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(new RedissonConnectionFactory(redisson));
|
||||
// 2.2 设置序列化器
|
||||
template.setKeySerializer(RedisSerializer.string());
|
||||
template.setHashKeySerializer(RedisSerializer.string());
|
||||
template.setValueSerializer(RedisSerializer.json());
|
||||
template.setHashValueSerializer(RedisSerializer.json());
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void closeProducer(RedisTemplate<String, Object> producer) throws Exception {
|
||||
RedisConnectionFactory factory = producer.getConnectionFactory();
|
||||
if (factory != null) {
|
||||
((RedissonConnectionFactory) factory).destroy();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,552 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.text.CharPool;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleScenePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotRuleSceneMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneConditionOperatorEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotSceneRuleAction;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.RULE_SCENE_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* IoT 规则场景 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class IotRuleSceneServiceImpl implements IotRuleSceneService {
|
||||
|
||||
@Resource
|
||||
private IotRuleSceneMapper ruleSceneMapper;
|
||||
|
||||
@Resource
|
||||
private List<IotSceneRuleAction> ruleSceneActions;
|
||||
|
||||
@Resource(name = "iotSchedulerManager")
|
||||
private IotSchedulerManager schedulerManager;
|
||||
|
||||
@Resource
|
||||
private IotProductService productService;
|
||||
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
|
||||
@Override
|
||||
public Long createRuleScene(IotRuleSceneSaveReqVO createReqVO) {
|
||||
IotSceneRuleDO ruleScene = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class);
|
||||
ruleSceneMapper.insert(ruleScene);
|
||||
return ruleScene.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRuleScene(IotRuleSceneSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateRuleSceneExists(updateReqVO.getId());
|
||||
// 更新
|
||||
IotSceneRuleDO updateObj = BeanUtils.toBean(updateReqVO, IotSceneRuleDO.class);
|
||||
ruleSceneMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRuleSceneStatus(Long id, Integer status) {
|
||||
// 校验存在
|
||||
validateRuleSceneExists(id);
|
||||
// 更新状态
|
||||
IotSceneRuleDO updateObj = new IotSceneRuleDO().setId(id).setStatus(status);
|
||||
ruleSceneMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRuleScene(Long id) {
|
||||
// 校验存在
|
||||
validateRuleSceneExists(id);
|
||||
// 删除
|
||||
ruleSceneMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void validateRuleSceneExists(Long id) {
|
||||
if (ruleSceneMapper.selectById(id) == null) {
|
||||
throw exception(RULE_SCENE_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleDO getRuleScene(Long id) {
|
||||
return ruleSceneMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<IotSceneRuleDO> getRuleScenePage(IotRuleScenePageReqVO pageReqVO) {
|
||||
return ruleSceneMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRuleSceneList(Collection<Long> ids) {
|
||||
if (CollUtil.isEmpty(ids)) {
|
||||
return;
|
||||
}
|
||||
// 批量查询存在的规则场景
|
||||
List<IotSceneRuleDO> existingScenes = ruleSceneMapper.selectByIds(ids);
|
||||
if (existingScenes.size() != ids.size()) {
|
||||
throw exception(RULE_SCENE_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotSceneRuleDO> getRuleSceneListByStatus(Integer status) {
|
||||
return ruleSceneMapper.selectListByStatus(status);
|
||||
}
|
||||
|
||||
// TODO 芋艿,缓存待实现
|
||||
@Override
|
||||
@TenantIgnore // 忽略租户隔离:因为 IotRuleSceneMessageHandler 调用时,一般未传递租户,所以需要忽略
|
||||
public List<IotSceneRuleDO> getRuleSceneListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) {
|
||||
// TODO @puhui999:一些注释,看看要不要优化下;
|
||||
// 注意:旧的测试代码已删除,因为使用了废弃的数据结构
|
||||
// 如需测试,请使用上面的新结构测试代码示例
|
||||
List<IotSceneRuleDO> list = ruleSceneMapper.selectList();
|
||||
// 只返回启用状态的规则场景
|
||||
List<IotSceneRuleDO> enabledList = filterList(list,
|
||||
ruleScene -> CommonStatusEnum.ENABLE.getStatus().equals(ruleScene.getStatus()));
|
||||
|
||||
// 根据 productKey 和 deviceName 进行匹配
|
||||
return filterList(enabledList, ruleScene -> {
|
||||
if (CollUtil.isEmpty(ruleScene.getTriggers())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (IotSceneRuleDO.Trigger trigger : ruleScene.getTriggers()) {
|
||||
// 检查触发器是否匹配指定的产品和设备
|
||||
if (isMatchProductAndDevice(trigger, productKey, deviceName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查触发器是否匹配指定的产品和设备
|
||||
*
|
||||
* @param trigger 触发器
|
||||
* @param productKey 产品标识
|
||||
* @param deviceName 设备名称
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean isMatchProductAndDevice(IotSceneRuleDO.Trigger trigger, String productKey, String deviceName) {
|
||||
try {
|
||||
// 1. 检查产品是否匹配
|
||||
if (trigger.getProductId() != null) {
|
||||
// 通过 productKey 获取产品信息
|
||||
IotProductDO product = productService.getProductByProductKey(productKey);
|
||||
if (product == null || !trigger.getProductId().equals(product.getId())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查设备是否匹配
|
||||
if (trigger.getDeviceId() != null) {
|
||||
// 通过 productKey 和 deviceName 获取设备信息
|
||||
IotDeviceDO device = deviceService.getDeviceFromCache(productKey, deviceName);
|
||||
if (device == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否是全部设备的特殊标识
|
||||
if (IotDeviceDO.DEVICE_ID_ALL.equals(trigger.getDeviceId())) {
|
||||
return true; // 匹配所有设备
|
||||
}
|
||||
|
||||
// 检查具体设备ID是否匹配
|
||||
if (!trigger.getDeviceId().equals(device.getId())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.warn("[isMatchProductAndDevice][产品({}) 设备({}) 匹配触发器异常]", productKey, deviceName, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeRuleSceneByDevice(IotDeviceMessage message) {
|
||||
// TODO @芋艿:这里的 tenantId,通过设备获取;
|
||||
TenantUtils.execute(message.getTenantId(), () -> {
|
||||
// 1. 获得设备匹配的规则场景
|
||||
List<IotSceneRuleDO> ruleScenes = getMatchedRuleSceneListByMessage(message);
|
||||
if (CollUtil.isEmpty(ruleScenes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 执行规则场景
|
||||
executeRuleSceneAction(message, ruleScenes);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeRuleSceneByTimer(Long id) {
|
||||
// 1.1 获得规则场景
|
||||
IotSceneRuleDO scene = TenantUtils.executeIgnore(() -> ruleSceneMapper.selectById(id));
|
||||
if (scene == null) {
|
||||
log.error("[executeRuleSceneByTimer][规则场景({}) 不存在]", id);
|
||||
return;
|
||||
}
|
||||
if (CommonStatusEnum.isDisable(scene.getStatus())) {
|
||||
log.info("[executeRuleSceneByTimer][规则场景({}) 已被禁用]", id);
|
||||
return;
|
||||
}
|
||||
// 1.2 判断是否有定时触发器,避免脏数据
|
||||
IotSceneRuleDO.Trigger config = CollUtil.findOne(scene.getTriggers(),
|
||||
trigger -> ObjUtil.equals(trigger.getType(), IotRuleSceneTriggerTypeEnum.TIMER.getType()));
|
||||
if (config == null) {
|
||||
log.error("[executeRuleSceneByTimer][规则场景({}) 不存在定时触发器]", scene);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 执行规则场景
|
||||
TenantUtils.execute(scene.getTenantId(),
|
||||
() -> executeRuleSceneAction(null, ListUtil.toList(scene)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于消息,获得匹配的规则场景列表
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @return 规则场景列表
|
||||
*/
|
||||
private List<IotSceneRuleDO> getMatchedRuleSceneListByMessage(IotDeviceMessage message) {
|
||||
// 1. 匹配设备
|
||||
// TODO @芋艿:可能需要 getSelf(); 缓存
|
||||
// 1.1 通过 deviceId 获取设备信息
|
||||
IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId());
|
||||
if (device == null) {
|
||||
log.warn("[getMatchedRuleSceneListByMessage][设备({}) 不存在]", message.getDeviceId());
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// 1.2 通过 productId 获取产品信息
|
||||
IotProductDO product = productService.getProductFromCache(device.getProductId());
|
||||
if (product == null) {
|
||||
log.warn("[getMatchedRuleSceneListByMessage][产品({}) 不存在]", device.getProductId());
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// 1.3 获取匹配的规则场景
|
||||
List<IotSceneRuleDO> ruleScenes = getRuleSceneListByProductKeyAndDeviceNameFromCache(
|
||||
product.getProductKey(), device.getDeviceName());
|
||||
if (CollUtil.isEmpty(ruleScenes)) {
|
||||
return ruleScenes;
|
||||
}
|
||||
|
||||
// 2. 匹配 trigger 触发器的条件
|
||||
return filterList(ruleScenes, ruleScene -> {
|
||||
for (IotSceneRuleDO.Trigger trigger : ruleScene.getTriggers()) {
|
||||
// 2.1 检查触发器类型,根据新的枚举值进行匹配
|
||||
// TODO @芋艿:需要根据新的触发器类型枚举进行适配
|
||||
// 原来使用 IotRuleSceneTriggerTypeEnum.DEVICE,新结构可能有不同的类型
|
||||
|
||||
// 2.2 条件分组为空,说明没有匹配的条件,因此不匹配
|
||||
if (CollUtil.isEmpty(trigger.getConditionGroups())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.3 检查条件分组:分组与分组之间是"或"的关系,条件与条件之间是"且"的关系
|
||||
boolean anyGroupMatched = false;
|
||||
for (List<IotSceneRuleDO.TriggerCondition> conditionGroup : trigger.getConditionGroups()) {
|
||||
if (CollUtil.isEmpty(conditionGroup)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查当前分组中的所有条件是否都匹配(且关系)
|
||||
boolean allConditionsMatched = true;
|
||||
for (IotSceneRuleDO.TriggerCondition condition : conditionGroup) {
|
||||
// TODO @芋艿:这里需要实现具体的条件匹配逻辑
|
||||
// 根据新的 TriggerCondition 结构进行匹配
|
||||
if (!isTriggerConditionMatched(message, condition, ruleScene, trigger)) {
|
||||
allConditionsMatched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allConditionsMatched) {
|
||||
anyGroupMatched = true;
|
||||
break; // 有一个分组匹配即可
|
||||
}
|
||||
}
|
||||
|
||||
if (anyGroupMatched) {
|
||||
log.info("[getMatchedRuleSceneList][消息({}) 匹配到规则场景编号({}) 的触发器({})]", message, ruleScene.getId(), trigger);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于消息,判断触发器的条件是否匹配
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @param ruleScene 规则场景(用于日志,无其它作用)
|
||||
* @param trigger 触发器(用于日志,无其它作用)
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean isTriggerConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition,
|
||||
IotSceneRuleDO ruleScene, IotSceneRuleDO.Trigger trigger) {
|
||||
try {
|
||||
// 1. 根据条件类型进行匹配
|
||||
if (IotRuleSceneConditionTypeEnum.DEVICE_STATE.getType().equals(condition.getType())) {
|
||||
// 设备状态条件匹配
|
||||
return matchDeviceStateCondition(message, condition);
|
||||
} else if (IotRuleSceneConditionTypeEnum.DEVICE_PROPERTY.getType().equals(condition.getType())) {
|
||||
// 设备属性条件匹配
|
||||
return matchDevicePropertyCondition(message, condition);
|
||||
} else if (IotRuleSceneConditionTypeEnum.CURRENT_TIME.getType().equals(condition.getType())) {
|
||||
// 当前时间条件匹配
|
||||
return matchCurrentTimeCondition(condition);
|
||||
} else {
|
||||
log.warn("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 存在未知的条件类型({})]",
|
||||
ruleScene.getId(), trigger, condition.getType());
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 条件匹配异常]",
|
||||
ruleScene.getId(), trigger, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配设备状态条件
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean matchDeviceStateCondition(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
// TODO @芋艿:需要根据设备状态进行匹配
|
||||
// 这里需要检查消息中的设备状态是否符合条件中定义的状态
|
||||
log.debug("[matchDeviceStateCondition][设备状态条件匹配逻辑待实现] condition: {}", condition);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配设备属性条件
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean matchDevicePropertyCondition(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
// 1. 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (StrUtil.isBlank(condition.getIdentifier()) || !condition.getIdentifier().equals(messageIdentifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 获取消息中的属性值
|
||||
Object messageValue = message.getData();
|
||||
if (messageValue == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 根据操作符进行匹配
|
||||
return evaluateCondition(messageValue, condition.getOperator(), condition.getParam());
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配当前时间条件
|
||||
*
|
||||
* @param condition 触发条件
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean matchCurrentTimeCondition(IotSceneRuleDO.TriggerCondition condition) {
|
||||
// TODO @芋艿:需要根据当前时间进行匹配
|
||||
// 这里需要检查当前时间是否符合条件中定义的时间范围
|
||||
log.debug("[matchCurrentTimeCondition][当前时间条件匹配逻辑待实现] condition: {}", condition);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 评估条件是否匹配
|
||||
*
|
||||
* @param sourceValue 源值(来自消息)
|
||||
* @param operator 操作符
|
||||
* @param paramValue 参数值(来自条件配置)
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean evaluateCondition(Object sourceValue, String operator, String paramValue) {
|
||||
try {
|
||||
// 1. 校验操作符是否合法
|
||||
IotRuleSceneConditionOperatorEnum operatorEnum = IotRuleSceneConditionOperatorEnum.operatorOf(operator);
|
||||
if (operatorEnum == null) {
|
||||
log.warn("[evaluateCondition][存在错误的操作符({})]", operator);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1 构建 Spring 表达式的变量
|
||||
Map<String, Object> springExpressionVariables = MapUtil.<String, Object>builder()
|
||||
.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue)
|
||||
.build();
|
||||
// 2.2 根据操作符类型处理参数值
|
||||
if (StrUtil.isNotBlank(paramValue)) {
|
||||
// TODO @puhui999:这里是不是在 IotRuleSceneConditionOperatorEnum 加个属性;
|
||||
if (operatorEnum == IotRuleSceneConditionOperatorEnum.IN
|
||||
|| operatorEnum == IotRuleSceneConditionOperatorEnum.NOT_IN
|
||||
|| operatorEnum == IotRuleSceneConditionOperatorEnum.BETWEEN
|
||||
|| operatorEnum == IotRuleSceneConditionOperatorEnum.NOT_BETWEEN) {
|
||||
// 处理多值情况
|
||||
List<String> paramValues = StrUtil.split(paramValue, CharPool.COMMA);
|
||||
springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST,
|
||||
convertList(paramValues, NumberUtil::parseDouble));
|
||||
} else {
|
||||
// 处理单值情况
|
||||
springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE,
|
||||
NumberUtil.parseDouble(paramValue));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 计算 Spring 表达式
|
||||
return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables);
|
||||
} catch (Exception e) {
|
||||
log.error("[evaluateCondition][条件评估异常] sourceValue: {}, operator: {}, paramValue: {}",
|
||||
sourceValue, operator, paramValue, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @芋艿:【可优化】可以考虑增加下单测,边界太多了。
|
||||
|
||||
/**
|
||||
* 判断触发器的条件参数是否匹配
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @param ruleScene 规则场景(用于日志,无其它作用)
|
||||
* @param trigger 触发器(用于日志,无其它作用)
|
||||
* @return 是否匹配
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "DataFlowIssue"})
|
||||
private boolean isTriggerConditionParameterMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition,
|
||||
IotSceneRuleDO ruleScene, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1.1 校验操作符是否合法
|
||||
IotRuleSceneConditionOperatorEnum operator =
|
||||
IotRuleSceneConditionOperatorEnum.operatorOf(condition.getOperator());
|
||||
if (operator == null) {
|
||||
log.error("[isTriggerConditionParameterMatched][规则场景编号({}) 的触发器({}) 存在错误的操作符({})]",
|
||||
ruleScene.getId(), trigger, condition.getOperator());
|
||||
return false;
|
||||
}
|
||||
// 1.2 校验消息是否包含对应的值
|
||||
String messageValue = MapUtil.getStr((Map<String, Object>) message.getData(), condition.getIdentifier());
|
||||
if (messageValue == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1 构建 Spring 表达式的变量
|
||||
Map<String, Object> springExpressionVariables = new HashMap<>();
|
||||
try {
|
||||
springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, messageValue);
|
||||
springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE, condition.getParam());
|
||||
List<String> parameterValues = StrUtil.splitTrim(condition.getParam(), CharPool.COMMA);
|
||||
springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues);
|
||||
// 特殊:解决数字的比较。因为 Spring 是基于它的 compareTo 方法,对数字的比较存在问题!
|
||||
if (ObjectUtils.equalsAny(operator, IotRuleSceneConditionOperatorEnum.BETWEEN,
|
||||
IotRuleSceneConditionOperatorEnum.NOT_BETWEEN,
|
||||
IotRuleSceneConditionOperatorEnum.GREATER_THAN,
|
||||
IotRuleSceneConditionOperatorEnum.GREATER_THAN_OR_EQUALS,
|
||||
IotRuleSceneConditionOperatorEnum.LESS_THAN,
|
||||
IotRuleSceneConditionOperatorEnum.LESS_THAN_OR_EQUALS)
|
||||
&& NumberUtil.isNumber(messageValue)
|
||||
&& NumberUtils.isAllNumber(parameterValues)) {
|
||||
springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_SOURCE,
|
||||
NumberUtil.parseDouble(messageValue));
|
||||
springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE,
|
||||
NumberUtil.parseDouble(condition.getParam()));
|
||||
springExpressionVariables.put(IotRuleSceneConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST,
|
||||
convertList(parameterValues, NumberUtil::parseDouble));
|
||||
}
|
||||
// 2.2 计算 Spring 表达式
|
||||
return (Boolean) SpringExpressionUtils.parseExpression(operator.getSpringExpression(), springExpressionVariables);
|
||||
} catch (Exception e) {
|
||||
log.error("[isTriggerConditionParameterMatched][消息({}) 规则场景编号({}) 的触发器({}) 的匹配表达式({}/{}) 计算异常]",
|
||||
message, ruleScene.getId(), trigger, operator, springExpressionVariables, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行规则场景的动作
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param ruleScenes 规则场景列表
|
||||
*/
|
||||
private void executeRuleSceneAction(IotDeviceMessage message, List<IotSceneRuleDO> ruleScenes) {
|
||||
// 1. 遍历规则场景
|
||||
ruleScenes.forEach(ruleScene -> {
|
||||
// 2. 遍历规则场景的动作
|
||||
ruleScene.getActions().forEach(actionConfig -> {
|
||||
// 3.1 获取对应的动作 Action 数组
|
||||
List<IotSceneRuleAction> actions = filterList(ruleSceneActions,
|
||||
action -> action.getType().getType().equals(actionConfig.getType()));
|
||||
if (CollUtil.isEmpty(actions)) {
|
||||
return;
|
||||
}
|
||||
// 3.2 执行动作
|
||||
actions.forEach(action -> {
|
||||
try {
|
||||
action.execute(message, ruleScene, actionConfig);
|
||||
log.info("[executeRuleSceneAction][消息({}) 规则场景编号({}) 的执行动作({}) 成功]",
|
||||
message, ruleScene.getId(), actionConfig);
|
||||
} catch (Exception e) {
|
||||
log.error("[executeRuleSceneAction][消息({}) 规则场景编号({}) 的执行动作({}) 执行异常]",
|
||||
message, ruleScene.getId(), actionConfig, e);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleScenePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.Collection;
|
||||
@ -16,7 +16,7 @@ import java.util.List;
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface IotRuleSceneService {
|
||||
public interface IotSceneRuleService {
|
||||
|
||||
/**
|
||||
* 创建场景联动
|
||||
@ -24,14 +24,14 @@ public interface IotRuleSceneService {
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createRuleScene(@Valid IotRuleSceneSaveReqVO createReqVO);
|
||||
Long createSceneRule(@Valid IotSceneRuleSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新场景联动
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateRuleScene(@Valid IotRuleSceneSaveReqVO updateReqVO);
|
||||
void updateSceneRule(@Valid IotSceneRuleSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 更新场景联动状态
|
||||
@ -39,14 +39,14 @@ public interface IotRuleSceneService {
|
||||
* @param id 场景联动编号
|
||||
* @param status 状态
|
||||
*/
|
||||
void updateRuleSceneStatus(Long id, Integer status);
|
||||
void updateSceneRuleStatus(Long id, Integer status);
|
||||
|
||||
/**
|
||||
* 删除场景联动
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteRuleScene(Long id);
|
||||
void deleteSceneRule(Long id);
|
||||
|
||||
/**
|
||||
* 获得场景联动
|
||||
@ -54,7 +54,7 @@ public interface IotRuleSceneService {
|
||||
* @param id 编号
|
||||
* @return 场景联动
|
||||
*/
|
||||
IotSceneRuleDO getRuleScene(Long id);
|
||||
IotSceneRuleDO getSceneRule(Long id);
|
||||
|
||||
/**
|
||||
* 获得场景联动分页
|
||||
@ -62,7 +62,7 @@ public interface IotRuleSceneService {
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 场景联动分页
|
||||
*/
|
||||
PageResult<IotSceneRuleDO> getRuleScenePage(IotRuleScenePageReqVO pageReqVO);
|
||||
PageResult<IotSceneRuleDO> getSceneRulePage(IotSceneRulePageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 校验规则场景联动规则编号们是否存在。如下情况,视为无效:
|
||||
@ -70,7 +70,7 @@ public interface IotRuleSceneService {
|
||||
*
|
||||
* @param ids 场景联动规则编号数组
|
||||
*/
|
||||
void validateRuleSceneList(Collection<Long> ids);
|
||||
void validateSceneRuleList(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得指定状态的场景联动列表
|
||||
@ -78,29 +78,33 @@ public interface IotRuleSceneService {
|
||||
* @param status 状态
|
||||
* @return 场景联动列表
|
||||
*/
|
||||
List<IotSceneRuleDO> getRuleSceneListByStatus(Integer status);
|
||||
List<IotSceneRuleDO> getSceneRuleListByStatus(Integer status);
|
||||
|
||||
/**
|
||||
* 【缓存】获得指定设备的场景列表
|
||||
*
|
||||
* @param productKey 产品 Key
|
||||
* @param deviceName 设备名称
|
||||
* @param productId 产品 ID
|
||||
* @param deviceId 设备 ID
|
||||
* @return 场景列表
|
||||
*/
|
||||
List<IotSceneRuleDO> getRuleSceneListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName);
|
||||
List<IotSceneRuleDO> getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId);
|
||||
|
||||
/**
|
||||
* 基于 {@link IotRuleSceneTriggerTypeEnum#DEVICE} 场景,执行规则场景
|
||||
*
|
||||
* 基于 {@link IotSceneRuleTriggerTypeEnum} 场景,执行规则场景
|
||||
* 1. {@link IotSceneRuleTriggerTypeEnum#DEVICE_STATE_UPDATE}
|
||||
* 2. {@link IotSceneRuleTriggerTypeEnum#DEVICE_PROPERTY_POST}
|
||||
* {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST}
|
||||
* 3. {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST}
|
||||
* {@link IotSceneRuleTriggerTypeEnum#DEVICE_SERVICE_INVOKE}
|
||||
* @param message 消息
|
||||
*/
|
||||
void executeRuleSceneByDevice(IotDeviceMessage message);
|
||||
void executeSceneRuleByDevice(IotDeviceMessage message);
|
||||
|
||||
/**
|
||||
* 基于 {@link IotRuleSceneTriggerTypeEnum#TIMER} 场景,执行规则场景
|
||||
* 基于 {@link IotSceneRuleTriggerTypeEnum#TIMER} 场景,执行规则场景
|
||||
*
|
||||
* @param id 场景联动规则编号
|
||||
*/
|
||||
void executeRuleSceneByTimer(Long id);
|
||||
void executeSceneRuleByTimer(Long id);
|
||||
|
||||
}
|
||||
@ -0,0 +1,450 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.text.CharPool;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotSceneRuleAction;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.RULE_SCENE_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* IoT 规则场景 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class IotSceneRuleServiceImpl implements IotSceneRuleService {
|
||||
|
||||
@Resource
|
||||
private IotSceneRuleMapper sceneRuleMapper;
|
||||
|
||||
@Resource(name = "iotSchedulerManager")
|
||||
private IotSchedulerManager schedulerManager;
|
||||
@Resource
|
||||
private IotProductService productService;
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
@Resource
|
||||
private IotSceneRuleMatcherManager matcherManager;
|
||||
@Resource
|
||||
private List<IotSceneRuleAction> sceneRuleActions;
|
||||
|
||||
@Override
|
||||
public Long createSceneRule(IotSceneRuleSaveReqVO createReqVO) {
|
||||
IotSceneRuleDO sceneRule = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class);
|
||||
sceneRuleMapper.insert(sceneRule);
|
||||
return sceneRule.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSceneRule(IotSceneRuleSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateSceneRuleExists(updateReqVO.getId());
|
||||
// 更新
|
||||
IotSceneRuleDO updateObj = BeanUtils.toBean(updateReqVO, IotSceneRuleDO.class);
|
||||
sceneRuleMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSceneRuleStatus(Long id, Integer status) {
|
||||
// 校验存在
|
||||
validateSceneRuleExists(id);
|
||||
// 更新状态
|
||||
IotSceneRuleDO updateObj = new IotSceneRuleDO().setId(id).setStatus(status);
|
||||
sceneRuleMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSceneRule(Long id) {
|
||||
// 校验存在
|
||||
validateSceneRuleExists(id);
|
||||
// 删除
|
||||
sceneRuleMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private void validateSceneRuleExists(Long id) {
|
||||
if (sceneRuleMapper.selectById(id) == null) {
|
||||
throw exception(RULE_SCENE_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleDO getSceneRule(Long id) {
|
||||
return sceneRuleMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<IotSceneRuleDO> getSceneRulePage(IotSceneRulePageReqVO pageReqVO) {
|
||||
return sceneRuleMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateSceneRuleList(Collection<Long> ids) {
|
||||
if (CollUtil.isEmpty(ids)) {
|
||||
return;
|
||||
}
|
||||
// 批量查询存在的规则场景
|
||||
List<IotSceneRuleDO> existingScenes = sceneRuleMapper.selectByIds(ids);
|
||||
if (existingScenes.size() != ids.size()) {
|
||||
throw exception(RULE_SCENE_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IotSceneRuleDO> getSceneRuleListByStatus(Integer status) {
|
||||
return sceneRuleMapper.selectListByStatus(status);
|
||||
}
|
||||
|
||||
// TODO 芋艿,缓存待实现
|
||||
@Override
|
||||
@TenantIgnore // 忽略租户隔离:因为 IotSceneRuleMessageHandler 调用时,一般未传递租户,所以需要忽略
|
||||
public List<IotSceneRuleDO> getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId) {
|
||||
List<IotSceneRuleDO> list = sceneRuleMapper.selectList();
|
||||
// 只返回启用状态的规则场景
|
||||
List<IotSceneRuleDO> enabledList = filterList(list,
|
||||
sceneRule -> CommonStatusEnum.isEnable(sceneRule.getStatus()));
|
||||
|
||||
// 根据 productKey 和 deviceName 进行匹配
|
||||
return filterList(enabledList, sceneRule -> {
|
||||
if (CollUtil.isEmpty(sceneRule.getTriggers())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (IotSceneRuleDO.Trigger trigger : sceneRule.getTriggers()) {
|
||||
// 检查触发器是否匹配指定的产品和设备
|
||||
try {
|
||||
// 1. 检查产品是否匹配
|
||||
if (trigger.getProductId() == null) {
|
||||
return false;
|
||||
}
|
||||
if (trigger.getDeviceId() == null) {
|
||||
return false;
|
||||
}
|
||||
// 检查是否是全部设备的特殊标识
|
||||
if (IotDeviceDO.DEVICE_ID_ALL.equals(trigger.getDeviceId())) {
|
||||
return true; // 匹配所有设备
|
||||
}
|
||||
// 检查具体设备 ID 是否匹配
|
||||
return ObjUtil.equal(productId, trigger.getProductId()) && ObjUtil.equal(deviceId, trigger.getDeviceId());
|
||||
} catch (Exception e) {
|
||||
log.warn("[isMatchProductAndDevice][产品({}) 设备({}) 匹配触发器异常]", productId, deviceId, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeSceneRuleByDevice(IotDeviceMessage message) {
|
||||
// TODO @芋艿:这里的 tenantId,通过设备获取;
|
||||
TenantUtils.execute(message.getTenantId(), () -> {
|
||||
// 1. 获得设备匹配的规则场景
|
||||
List<IotSceneRuleDO> sceneRules = getMatchedSceneRuleListByMessage(message);
|
||||
if (CollUtil.isEmpty(sceneRules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 执行规则场景
|
||||
executeSceneRuleAction(message, sceneRules);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeSceneRuleByTimer(Long id) {
|
||||
// 1.1 获得规则场景
|
||||
IotSceneRuleDO scene = TenantUtils.executeIgnore(() -> sceneRuleMapper.selectById(id));
|
||||
if (scene == null) {
|
||||
log.error("[executeSceneRuleByTimer][规则场景({}) 不存在]", id);
|
||||
return;
|
||||
}
|
||||
if (CommonStatusEnum.isDisable(scene.getStatus())) {
|
||||
log.info("[executeSceneRuleByTimer][规则场景({}) 已被禁用]", id);
|
||||
return;
|
||||
}
|
||||
// 1.2 判断是否有定时触发器,避免脏数据
|
||||
IotSceneRuleDO.Trigger config = CollUtil.findOne(scene.getTriggers(),
|
||||
trigger -> ObjUtil.equals(trigger.getType(), IotSceneRuleTriggerTypeEnum.TIMER.getType()));
|
||||
if (config == null) {
|
||||
log.error("[executeSceneRuleByTimer][规则场景({}) 不存在定时触发器]", scene);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 执行规则场景
|
||||
TenantUtils.execute(scene.getTenantId(),
|
||||
() -> executeSceneRuleAction(null, ListUtil.toList(scene)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于消息,获得匹配的规则场景列表
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @return 规则场景列表
|
||||
*/
|
||||
private List<IotSceneRuleDO> getMatchedSceneRuleListByMessage(IotDeviceMessage message) {
|
||||
// 1. 匹配设备
|
||||
// TODO @芋艿:可能需要 getSelf(); 缓存
|
||||
// 1.1 通过 deviceId 获取设备信息
|
||||
IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId());
|
||||
if (device == null) {
|
||||
log.warn("[getMatchedSceneRuleListByMessage][设备({}) 不存在]", message.getDeviceId());
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// 1.2 通过 productId 获取产品信息
|
||||
IotProductDO product = productService.getProductFromCache(device.getProductId());
|
||||
if (product == null) {
|
||||
log.warn("[getMatchedSceneRuleListByMessage][产品({}) 不存在]", device.getProductId());
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// 1.3 获取匹配的规则场景
|
||||
List<IotSceneRuleDO> sceneRules = getSceneRuleListByProductIdAndDeviceIdFromCache(
|
||||
product.getId(), device.getId());
|
||||
if (CollUtil.isEmpty(sceneRules)) {
|
||||
return sceneRules;
|
||||
}
|
||||
|
||||
// 2. 使用重构后的触发器匹配逻辑
|
||||
return filterList(sceneRules, sceneRule -> matchSceneRuleTriggers(message, sceneRule));
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配场景规则的所有触发器
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param sceneRule 场景规则
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean matchSceneRuleTriggers(IotDeviceMessage message, IotSceneRuleDO sceneRule) {
|
||||
if (CollUtil.isEmpty(sceneRule.getTriggers())) {
|
||||
log.debug("[matchSceneRuleTriggers][规则场景({}) 没有配置触发器]", sceneRule.getId());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (IotSceneRuleDO.Trigger trigger : sceneRule.getTriggers()) {
|
||||
if (matchSingleTrigger(message, trigger, sceneRule)) {
|
||||
log.info("[matchSceneRuleTriggers][消息({}) 匹配到规则场景编号({}) 的触发器({})]",
|
||||
message.getRequestId(), sceneRule.getId(), trigger.getType());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配单个触发器
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器
|
||||
* @param sceneRule 场景规则(用于日志)
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean matchSingleTrigger(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) {
|
||||
try {
|
||||
// 2. 检查触发器的条件分组
|
||||
return matcherManager.isMatched(message, trigger) && isTriggerConditionGroupsMatched(message, trigger, sceneRule);
|
||||
} catch (Exception e) {
|
||||
log.error("[matchSingleTrigger][触发器匹配异常] sceneRuleId: {}, triggerType: {}, message: {}",
|
||||
sceneRule.getId(), trigger.getType(), message, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查触发器的条件分组是否匹配
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器
|
||||
* @param sceneRule 场景规则(用于日志)
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean isTriggerConditionGroupsMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) {
|
||||
// 如果没有条件分组,则认为匹配成功(只依赖基础触发器匹配)
|
||||
if (CollUtil.isEmpty(trigger.getConditionGroups())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查条件分组:分组与分组之间是"或"的关系,条件与条件之间是"且"的关系
|
||||
for (List<IotSceneRuleDO.TriggerCondition> conditionGroup : trigger.getConditionGroups()) {
|
||||
if (CollUtil.isEmpty(conditionGroup)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查当前分组中的所有条件是否都匹配(且关系)
|
||||
boolean allConditionsMatched = true;
|
||||
for (IotSceneRuleDO.TriggerCondition condition : conditionGroup) {
|
||||
if (!isTriggerConditionMatched(message, condition, sceneRule, trigger)) {
|
||||
allConditionsMatched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果当前分组的所有条件都匹配,则整个触发器匹配成功
|
||||
if (allConditionsMatched) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 所有分组都不匹配
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于消息,判断触发器的子条件是否匹配
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @param sceneRule 规则场景(用于日志,无其它作用)
|
||||
* @param trigger 触发器(用于日志,无其它作用)
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private boolean isTriggerConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition,
|
||||
IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) {
|
||||
try {
|
||||
// 使用重构后的条件匹配管理器进行匹配
|
||||
return matcherManager.isConditionMatched(message, condition);
|
||||
} catch (Exception e) {
|
||||
log.error("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 条件匹配异常]",
|
||||
sceneRule.getId(), trigger, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @芋艿:【可优化】可以考虑增加下单测,边界太多了。
|
||||
|
||||
/**
|
||||
* 判断触发器的条件参数是否匹配
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @param sceneRule 规则场景(用于日志,无其它作用)
|
||||
* @param trigger 触发器(用于日志,无其它作用)
|
||||
* @return 是否匹配
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "DataFlowIssue"})
|
||||
private boolean isTriggerConditionParameterMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition,
|
||||
IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1.1 校验操作符是否合法
|
||||
IotSceneRuleConditionOperatorEnum operator =
|
||||
IotSceneRuleConditionOperatorEnum.operatorOf(condition.getOperator());
|
||||
if (operator == null) {
|
||||
log.error("[isTriggerConditionParameterMatched][规则场景编号({}) 的触发器({}) 存在错误的操作符({})]",
|
||||
sceneRule.getId(), trigger, condition.getOperator());
|
||||
return false;
|
||||
}
|
||||
// 1.2 校验消息是否包含对应的值
|
||||
String messageValue = MapUtil.getStr((Map<String, Object>) message.getData(), condition.getIdentifier());
|
||||
if (messageValue == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1 构建 Spring 表达式的变量
|
||||
Map<String, Object> springExpressionVariables = new HashMap<>();
|
||||
try {
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, messageValue);
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, condition.getParam());
|
||||
List<String> parameterValues = StrUtil.splitTrim(condition.getParam(), CharPool.COMMA);
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues);
|
||||
// 特殊:解决数字的比较。因为 Spring 是基于它的 compareTo 方法,对数字的比较存在问题!
|
||||
if (ObjectUtils.equalsAny(operator, IotSceneRuleConditionOperatorEnum.BETWEEN,
|
||||
IotSceneRuleConditionOperatorEnum.NOT_BETWEEN,
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN,
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS,
|
||||
IotSceneRuleConditionOperatorEnum.LESS_THAN,
|
||||
IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS)
|
||||
&& NumberUtil.isNumber(messageValue)
|
||||
&& NumberUtils.isAllNumber(parameterValues)) {
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE,
|
||||
NumberUtil.parseDouble(messageValue));
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE,
|
||||
NumberUtil.parseDouble(condition.getParam()));
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST,
|
||||
convertList(parameterValues, NumberUtil::parseDouble));
|
||||
}
|
||||
// 2.2 计算 Spring 表达式
|
||||
return (Boolean) SpringExpressionUtils.parseExpression(operator.getSpringExpression(), springExpressionVariables);
|
||||
} catch (Exception e) {
|
||||
log.error("[isTriggerConditionParameterMatched][消息({}) 规则场景编号({}) 的触发器({}) 的匹配表达式({}/{}) 计算异常]",
|
||||
message, sceneRule.getId(), trigger, operator, springExpressionVariables, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行规则场景的动作
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param sceneRules 规则场景列表
|
||||
*/
|
||||
private void executeSceneRuleAction(IotDeviceMessage message, List<IotSceneRuleDO> sceneRules) {
|
||||
// 1. 遍历规则场景
|
||||
sceneRules.forEach(sceneRule -> {
|
||||
// 2. 遍历规则场景的动作
|
||||
sceneRule.getActions().forEach(actionConfig -> {
|
||||
// 3.1 获取对应的动作 Action 数组
|
||||
List<IotSceneRuleAction> actions = filterList(sceneRuleActions,
|
||||
action -> action.getType().getType().equals(actionConfig.getType()));
|
||||
if (CollUtil.isEmpty(actions)) {
|
||||
return;
|
||||
}
|
||||
// 3.2 执行动作
|
||||
actions.forEach(action -> {
|
||||
try {
|
||||
action.execute(message, sceneRule, actionConfig);
|
||||
log.info("[executeSceneRuleAction][消息({}) 规则场景编号({}) 的执行动作({}) 成功]",
|
||||
message, sceneRule.getId(), actionConfig);
|
||||
} catch (Exception e) {
|
||||
log.error("[executeSceneRuleAction][消息({}) 规则场景编号({}) 的执行动作({}) 执行异常]",
|
||||
message, sceneRule.getId(), actionConfig, e);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private IotSceneRuleServiceImpl getSelf() {
|
||||
return SpringUtil.getBean(IotSceneRuleServiceImpl.class);
|
||||
}
|
||||
|
||||
}
|
||||
@ -5,7 +5,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.alert.IotAlertRecordService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Component;
|
||||
@ -42,8 +42,8 @@ public class IotAlertRecoverSceneRuleAction implements IotSceneRuleAction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotRuleSceneActionTypeEnum getType() {
|
||||
return IotRuleSceneActionTypeEnum.ALERT_RECOVER;
|
||||
public IotSceneRuleActionTypeEnum getType() {
|
||||
return IotSceneRuleActionTypeEnum.ALERT_RECOVER;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.alert.IotAlertConfigService;
|
||||
import cn.iocoder.yudao.module.iot.service.alert.IotAlertRecordService;
|
||||
import cn.iocoder.yudao.module.system.api.mail.MailSendApi;
|
||||
@ -62,8 +62,8 @@ public class IotAlertTriggerSceneRuleAction implements IotSceneRuleAction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotRuleSceneActionTypeEnum getType() {
|
||||
return IotRuleSceneActionTypeEnum.ALERT_TRIGGER;
|
||||
public IotSceneRuleActionTypeEnum getType() {
|
||||
return IotSceneRuleActionTypeEnum.ALERT_TRIGGER;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.action;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
|
||||
import jakarta.annotation.Resource;
|
||||
@ -16,7 +16,7 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IotDeviceControlRuleSceneAction implements IotSceneRuleAction {
|
||||
public class IotDeviceControlSceneRuleAction implements IotSceneRuleAction {
|
||||
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
@ -48,8 +48,8 @@ public class IotDeviceControlRuleSceneAction implements IotSceneRuleAction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotRuleSceneActionTypeEnum getType() {
|
||||
return IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET;
|
||||
public IotSceneRuleActionTypeEnum getType() {
|
||||
return IotSceneRuleActionTypeEnum.DEVICE_PROPERTY_SET;
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.action;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@ -31,6 +31,6 @@ public interface IotSceneRuleAction {
|
||||
*
|
||||
* @return 类型
|
||||
*/
|
||||
IotRuleSceneActionTypeEnum getType();
|
||||
IotSceneRuleActionTypeEnum getType();
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,170 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
/**
|
||||
* IoT 场景规则匹配器抽象基类
|
||||
* <p>
|
||||
* 提供通用的条件评估逻辑和工具方法,支持触发器和条件两种匹配类型
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher {
|
||||
|
||||
/**
|
||||
* 评估条件是否匹配
|
||||
*
|
||||
* @param sourceValue 源值(来自消息)
|
||||
* @param operator 操作符
|
||||
* @param paramValue 参数值(来自条件配置)
|
||||
* @return 是否匹配
|
||||
*/
|
||||
protected boolean evaluateCondition(Object sourceValue, String operator, String paramValue) {
|
||||
try {
|
||||
// 1. 校验操作符是否合法
|
||||
IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator);
|
||||
if (operatorEnum == null) {
|
||||
log.warn("[evaluateCondition][存在错误的操作符({})]", operator);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 构建 Spring 表达式变量
|
||||
Map<String, Object> springExpressionVariables = new HashMap<>();
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue);
|
||||
|
||||
// 处理参数值
|
||||
if (StrUtil.isNotBlank(paramValue)) {
|
||||
// 处理多值情况(如 IN、BETWEEN 操作符)
|
||||
if (paramValue.contains(",")) {
|
||||
List<String> paramValues = StrUtil.split(paramValue, ',');
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST,
|
||||
convertList(paramValues, NumberUtil::parseDouble));
|
||||
} else {
|
||||
// 处理单值情况
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE,
|
||||
NumberUtil.parseDouble(paramValue));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 计算 Spring 表达式
|
||||
return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables);
|
||||
} catch (Exception e) {
|
||||
log.error("[evaluateCondition][条件评估异常] sourceValue: {}, operator: {}, paramValue: {}",
|
||||
sourceValue, operator, paramValue, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 触发器相关工具方法 ==========
|
||||
|
||||
/**
|
||||
* 检查基础触发器参数是否有效
|
||||
*
|
||||
* @param trigger 触发器配置
|
||||
* @return 是否有效
|
||||
*/
|
||||
protected boolean isBasicTriggerValid(IotSceneRuleDO.Trigger trigger) {
|
||||
return trigger != null && trigger.getType() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查触发器操作符和值是否有效
|
||||
*
|
||||
* @param trigger 触发器配置
|
||||
* @return 是否有效
|
||||
*/
|
||||
protected boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) {
|
||||
return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录触发器匹配成功日志
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器配置
|
||||
*/
|
||||
protected void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录触发器匹配失败日志
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器配置
|
||||
* @param reason 失败原因
|
||||
*/
|
||||
protected void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) {
|
||||
log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason);
|
||||
}
|
||||
|
||||
// ========== 条件相关工具方法 ==========
|
||||
|
||||
/**
|
||||
* 检查基础条件参数是否有效
|
||||
*
|
||||
* @param condition 触发条件
|
||||
* @return 是否有效
|
||||
*/
|
||||
protected boolean isBasicConditionValid(IotSceneRuleDO.TriggerCondition condition) {
|
||||
return condition != null && condition.getType() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查条件操作符和参数是否有效
|
||||
*
|
||||
* @param condition 触发条件
|
||||
* @return 是否有效
|
||||
*/
|
||||
protected boolean isConditionOperatorAndParamValid(IotSceneRuleDO.TriggerCondition condition) {
|
||||
return StrUtil.isNotBlank(condition.getOperator()) && StrUtil.isNotBlank(condition.getParam());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录条件匹配成功日志
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
*/
|
||||
protected void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
log.debug("[{}][消息({}) 匹配条件({}) 成功]", getMatcherName(), message.getRequestId(), condition.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录条件匹配失败日志
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @param reason 失败原因
|
||||
*/
|
||||
protected void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) {
|
||||
log.debug("[{}][消息({}) 匹配条件({}) 失败: {}]", getMatcherName(), message.getRequestId(), condition.getType(), reason);
|
||||
}
|
||||
|
||||
// ========== 通用工具方法 ==========
|
||||
|
||||
/**
|
||||
* 检查标识符是否匹配
|
||||
*
|
||||
* @param expectedIdentifier 期望的标识符
|
||||
* @param actualIdentifier 实际的标识符
|
||||
* @return 是否匹配
|
||||
*/
|
||||
protected boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) {
|
||||
return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,171 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 当前时间条件匹配器
|
||||
* <p>
|
||||
* 处理时间相关的子条件匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
/**
|
||||
* 时间格式化器 - HH:mm:ss
|
||||
*/
|
||||
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
|
||||
/**
|
||||
* 时间格式化器 - HH:mm
|
||||
*/
|
||||
private static final DateTimeFormatter TIME_FORMATTER_SHORT = DateTimeFormatter.ofPattern("HH:mm");
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.CONDITION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
|
||||
return IotSceneRuleConditionTypeEnum.CURRENT_TIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicConditionValid(condition)) {
|
||||
logConditionMatchFailure(message, condition, "条件基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查操作符和参数是否有效
|
||||
if (!isConditionOperatorAndParamValid(condition)) {
|
||||
logConditionMatchFailure(message, condition, "操作符或参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 获取当前时间
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// 4. 根据操作符类型进行不同的时间匹配
|
||||
String operator = condition.getOperator();
|
||||
String param = condition.getParam();
|
||||
|
||||
boolean matched = false;
|
||||
|
||||
try {
|
||||
if (operator.startsWith("date_time_")) {
|
||||
// 日期时间匹配(时间戳)
|
||||
matched = matchDateTime(now, operator, param);
|
||||
} else if (operator.startsWith("time_")) {
|
||||
// 当日时间匹配(HH:mm:ss)
|
||||
matched = matchTime(now.toLocalTime(), operator, param);
|
||||
} else {
|
||||
// 其他操作符,使用通用条件评估器
|
||||
matched = evaluateCondition(now.toEpochSecond(java.time.ZoneOffset.of("+8")), operator, param);
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
logConditionMatchSuccess(message, condition);
|
||||
} else {
|
||||
logConditionMatchFailure(message, condition, "时间条件不匹配");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[CurrentTimeConditionMatcher][时间条件匹配异常] operator: {}, param: {}", operator, param, e);
|
||||
logConditionMatchFailure(message, condition, "时间条件匹配异常: " + e.getMessage());
|
||||
matched = false;
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配日期时间(时间戳)
|
||||
*/
|
||||
private boolean matchDateTime(LocalDateTime now, String operator, String param) {
|
||||
long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8"));
|
||||
return evaluateCondition(currentTimestamp, operator.substring("date_time_".length()), param);
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配当日时间(HH:mm:ss)
|
||||
*/
|
||||
private boolean matchTime(LocalTime currentTime, String operator, String param) {
|
||||
try {
|
||||
String actualOperator = operator.substring("time_".length());
|
||||
|
||||
if ("between".equals(actualOperator)) {
|
||||
// 时间区间匹配
|
||||
String[] timeRange = param.split(",");
|
||||
if (timeRange.length != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LocalTime startTime = parseTime(timeRange[0].trim());
|
||||
LocalTime endTime = parseTime(timeRange[1].trim());
|
||||
|
||||
return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime);
|
||||
} else {
|
||||
// 单个时间比较
|
||||
LocalTime targetTime = parseTime(param);
|
||||
|
||||
switch (actualOperator) {
|
||||
case ">":
|
||||
return currentTime.isAfter(targetTime);
|
||||
case "<":
|
||||
return currentTime.isBefore(targetTime);
|
||||
case ">=":
|
||||
return !currentTime.isBefore(targetTime);
|
||||
case "<=":
|
||||
return !currentTime.isAfter(targetTime);
|
||||
case "=":
|
||||
return currentTime.equals(targetTime);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[CurrentTimeConditionMatcher][时间解析异常] param: {}", param, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析时间字符串
|
||||
*/
|
||||
private LocalTime parseTime(String timeStr) {
|
||||
if (StrUtil.isBlank(timeStr)) {
|
||||
throw new IllegalArgumentException("时间字符串不能为空");
|
||||
}
|
||||
|
||||
// 尝试不同的时间格式
|
||||
try {
|
||||
if (timeStr.length() == 5) { // HH:mm
|
||||
return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT);
|
||||
} else { // HH:mm:ss
|
||||
return LocalTime.parse(timeStr, TIME_FORMATTER);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("时间格式无效: " + timeStr, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 40; // 较低优先级
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备事件上报触发器匹配器
|
||||
* <p>
|
||||
* 处理设备事件上报的触发器匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
/**
|
||||
* 设备事件上报消息方法
|
||||
*/
|
||||
private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod();
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.TRIGGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicTriggerValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查消息方法是否匹配
|
||||
if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) {
|
||||
logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
|
||||
logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配
|
||||
// 但如果配置了操作符和值,则需要进行条件匹配
|
||||
if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) {
|
||||
Object eventData = message.getData();
|
||||
if (eventData == null) {
|
||||
logTriggerMatchFailure(message, trigger, "消息中事件数据为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue());
|
||||
if (!matched) {
|
||||
logTriggerMatchFailure(message, trigger, "事件数据条件不匹配");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
logTriggerMatchSuccess(message, trigger);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 30; // 中等优先级
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备属性条件匹配器
|
||||
* <p>
|
||||
* 处理设备属性相关的子条件匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.CONDITION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
|
||||
return IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicConditionValid(condition)) {
|
||||
logConditionMatchFailure(message, condition, "条件基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) {
|
||||
logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查操作符和参数是否有效
|
||||
if (!isConditionOperatorAndParamValid(condition)) {
|
||||
logConditionMatchFailure(message, condition, "操作符或参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 获取属性值
|
||||
Object propertyValue = message.getData();
|
||||
if (propertyValue == null) {
|
||||
logConditionMatchFailure(message, condition, "消息中属性值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. 使用条件评估器进行匹配
|
||||
boolean matched = evaluateCondition(propertyValue, condition.getOperator(), condition.getParam());
|
||||
|
||||
if (matched) {
|
||||
logConditionMatchSuccess(message, condition);
|
||||
} else {
|
||||
logConditionMatchFailure(message, condition, "设备属性条件不匹配");
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 25; // 中等优先级
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备属性上报触发器匹配器
|
||||
* <p>
|
||||
* 处理设备属性数据上报的触发器匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
/**
|
||||
* 设备属性上报消息方法
|
||||
*/
|
||||
private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod();
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.TRIGGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicTriggerValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查消息方法是否匹配
|
||||
if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) {
|
||||
logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
|
||||
logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 检查操作符和值是否有效
|
||||
if (!isTriggerOperatorAndValueValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "操作符或值无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. 获取属性值
|
||||
Object propertyValue = message.getData();
|
||||
if (propertyValue == null) {
|
||||
logTriggerMatchFailure(message, trigger, "消息中属性值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 6. 使用条件评估器进行匹配
|
||||
boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue());
|
||||
|
||||
if (matched) {
|
||||
logTriggerMatchSuccess(message, trigger);
|
||||
} else {
|
||||
logTriggerMatchFailure(message, trigger, "属性值条件不匹配");
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 20; // 中等优先级
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备服务调用触发器匹配器
|
||||
* <p>
|
||||
* 处理设备服务调用的触发器匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
/**
|
||||
* 设备服务调用消息方法
|
||||
*/
|
||||
private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod();
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.TRIGGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicTriggerValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查消息方法是否匹配
|
||||
if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) {
|
||||
logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
|
||||
logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 对于服务调用触发器,通常只需要匹配服务标识符即可
|
||||
// 不需要检查操作符和值,因为服务调用本身就是触发条件
|
||||
|
||||
logTriggerMatchSuccess(message, trigger);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 40; // 较低优先级
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备状态条件匹配器
|
||||
* <p>
|
||||
* 处理设备状态相关的子条件匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.CONDITION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
|
||||
return IotSceneRuleConditionTypeEnum.DEVICE_STATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicConditionValid(condition)) {
|
||||
logConditionMatchFailure(message, condition, "条件基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查操作符和参数是否有效
|
||||
if (!isConditionOperatorAndParamValid(condition)) {
|
||||
logConditionMatchFailure(message, condition, "操作符或参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 获取设备状态值
|
||||
// 设备状态通常在消息的 data 字段中
|
||||
Object stateValue = message.getData();
|
||||
if (stateValue == null) {
|
||||
logConditionMatchFailure(message, condition, "消息中设备状态值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 使用条件评估器进行匹配
|
||||
boolean matched = evaluateCondition(stateValue, condition.getOperator(), condition.getParam());
|
||||
|
||||
if (matched) {
|
||||
logConditionMatchSuccess(message, condition);
|
||||
} else {
|
||||
logConditionMatchFailure(message, condition, "设备状态条件不匹配");
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 30; // 中等优先级
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备状态更新触发器匹配器
|
||||
* <p>
|
||||
* 处理设备上下线状态变更的触发器匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
/**
|
||||
* 设备状态更新消息方法
|
||||
*/
|
||||
private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod();
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.TRIGGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicTriggerValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查消息方法是否匹配
|
||||
if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) {
|
||||
logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查操作符和值是否有效
|
||||
if (!isTriggerOperatorAndValueValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "操作符或值无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 获取设备状态值
|
||||
Object stateValue = message.getData();
|
||||
if (stateValue == null) {
|
||||
logTriggerMatchFailure(message, trigger, "消息中设备状态值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. 使用条件评估器进行匹配
|
||||
boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue());
|
||||
|
||||
if (matched) {
|
||||
logTriggerMatchSuccess(message, trigger);
|
||||
} else {
|
||||
logTriggerMatchFailure(message, trigger, "状态值条件不匹配");
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 10; // 高优先级
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
|
||||
/**
|
||||
* IoT 场景规则匹配器统一接口
|
||||
* <p>
|
||||
* 支持触发器匹配和条件匹配两种类型,遵循策略模式设计
|
||||
* <p>
|
||||
* 匹配器类型说明:
|
||||
* - 触发器匹配器:用于匹配主触发条件(如设备消息类型、定时器等)
|
||||
* - 条件匹配器:用于匹配子条件(如设备状态、属性值、时间条件等)
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public interface IotSceneRuleMatcher {
|
||||
|
||||
/**
|
||||
* 匹配器类型枚举
|
||||
*/
|
||||
enum MatcherType {
|
||||
/**
|
||||
* 触发器匹配器 - 用于匹配主触发条件
|
||||
*/
|
||||
TRIGGER,
|
||||
/**
|
||||
* 条件匹配器 - 用于匹配子条件
|
||||
*/
|
||||
CONDITION
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取匹配器类型
|
||||
*
|
||||
* @return 匹配器类型
|
||||
*/
|
||||
MatcherType getMatcherType();
|
||||
|
||||
/**
|
||||
* 获取支持的触发器类型(仅触发器匹配器需要实现)
|
||||
*
|
||||
* @return 触发器类型枚举,条件匹配器返回 null
|
||||
*/
|
||||
default IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支持的条件类型(仅条件匹配器需要实现)
|
||||
*
|
||||
* @return 条件类型枚举,触发器匹配器返回 null
|
||||
*/
|
||||
default IotSceneRuleConditionTypeEnum getSupportedConditionType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查触发器是否匹配消息(仅触发器匹配器需要实现)
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器配置
|
||||
* @return 是否匹配
|
||||
*/
|
||||
default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
throw new UnsupportedOperationException("触发器匹配方法仅支持触发器匹配器");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查条件是否匹配消息(仅条件匹配器需要实现)
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @return 是否匹配
|
||||
*/
|
||||
default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
throw new UnsupportedOperationException("条件匹配方法仅支持条件匹配器");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取匹配优先级(数值越小优先级越高)
|
||||
* <p>
|
||||
* 用于在多个匹配器支持同一类型时确定优先级
|
||||
*
|
||||
* @return 优先级数值
|
||||
*/
|
||||
default int getPriority() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取匹配器名称,用于日志和调试
|
||||
*
|
||||
* @return 匹配器名称
|
||||
*/
|
||||
default String getMatcherName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用该匹配器
|
||||
* <p>
|
||||
* 可用于动态开关某些匹配器
|
||||
*
|
||||
* @return 是否启用
|
||||
*/
|
||||
default boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,272 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum.findTriggerTypeEnum;
|
||||
|
||||
/**
|
||||
* IoT 场景规则匹配器统一管理器
|
||||
* <p>
|
||||
* 负责管理所有匹配器(触发器匹配器和条件匹配器),并提供统一的匹配入口
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IotSceneRuleMatcherManager {
|
||||
|
||||
/**
|
||||
* 触发器匹配器映射表
|
||||
* Key: 触发器类型枚举
|
||||
* Value: 对应的匹配器实例
|
||||
*/
|
||||
private final Map<IotSceneRuleTriggerTypeEnum, IotSceneRuleMatcher> triggerMatcherMap;
|
||||
|
||||
/**
|
||||
* 条件匹配器映射表
|
||||
* Key: 条件类型枚举
|
||||
* Value: 对应的匹配器实例
|
||||
*/
|
||||
private final Map<IotSceneRuleConditionTypeEnum, IotSceneRuleMatcher> conditionMatcherMap;
|
||||
|
||||
/**
|
||||
* 所有匹配器列表(按优先级排序)
|
||||
*/
|
||||
private final List<IotSceneRuleMatcher> allMatchers;
|
||||
|
||||
public IotSceneRuleMatcherManager(List<IotSceneRuleMatcher> matchers) {
|
||||
if (CollUtil.isEmpty(matchers)) {
|
||||
log.warn("[IotSceneRuleMatcherManager][没有找到任何匹配器]");
|
||||
this.triggerMatcherMap = new HashMap<>();
|
||||
this.conditionMatcherMap = new HashMap<>();
|
||||
this.allMatchers = new ArrayList<>();
|
||||
return;
|
||||
}
|
||||
|
||||
// 按优先级排序并过滤启用的匹配器
|
||||
this.allMatchers = matchers.stream()
|
||||
.filter(IotSceneRuleMatcher::isEnabled)
|
||||
.sorted(Comparator.comparing(IotSceneRuleMatcher::getPriority))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 分离触发器匹配器和条件匹配器
|
||||
List<IotSceneRuleMatcher> triggerMatchers = this.allMatchers.stream()
|
||||
.filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.TRIGGER)
|
||||
.toList();
|
||||
|
||||
List<IotSceneRuleMatcher> conditionMatchers = this.allMatchers.stream()
|
||||
.filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION)
|
||||
.toList();
|
||||
|
||||
// 构建触发器匹配器映射表
|
||||
this.triggerMatcherMap = triggerMatchers.stream()
|
||||
.collect(Collectors.toMap(
|
||||
IotSceneRuleMatcher::getSupportedTriggerType,
|
||||
Function.identity(),
|
||||
(existing, replacement) -> {
|
||||
log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]",
|
||||
existing.getSupportedTriggerType(),
|
||||
existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName());
|
||||
return existing.getPriority() <= replacement.getPriority() ? existing : replacement;
|
||||
},
|
||||
LinkedHashMap::new
|
||||
));
|
||||
|
||||
// 构建条件匹配器映射表
|
||||
this.conditionMatcherMap = conditionMatchers.stream()
|
||||
.collect(Collectors.toMap(
|
||||
IotSceneRuleMatcher::getSupportedConditionType,
|
||||
Function.identity(),
|
||||
(existing, replacement) -> {
|
||||
log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]",
|
||||
existing.getSupportedConditionType(),
|
||||
existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName());
|
||||
return existing.getPriority() <= replacement.getPriority() ? existing : replacement;
|
||||
},
|
||||
LinkedHashMap::new
|
||||
));
|
||||
|
||||
log.info("[IotSceneRuleMatcherManager][初始化完成,共加载 {} 个匹配器,其中触发器匹配器 {} 个,条件匹配器 {} 个]",
|
||||
this.allMatchers.size(), this.triggerMatcherMap.size(), this.conditionMatcherMap.size());
|
||||
|
||||
// 记录触发器匹配器详情
|
||||
this.triggerMatcherMap.forEach((type, matcher) ->
|
||||
log.info("[IotSceneRuleMatcherManager][触发器匹配器] 类型: {}, 匹配器: {}, 优先级: {}",
|
||||
type, matcher.getMatcherName(), matcher.getPriority()));
|
||||
|
||||
// 记录条件匹配器详情
|
||||
this.conditionMatcherMap.forEach((type, matcher) ->
|
||||
log.info("[IotSceneRuleMatcherManager][条件匹配器] 类型: {}, 匹配器: {}, 优先级: {}",
|
||||
type, matcher.getMatcherName(), matcher.getPriority()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查触发器是否匹配消息(主条件匹配)
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器配置
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
if (message == null || trigger == null || trigger.getType() == null) {
|
||||
log.debug("[isMatched][参数无效] message: {}, trigger: {}", message, trigger);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据触发器类型查找对应的匹配器
|
||||
IotSceneRuleTriggerTypeEnum triggerType = findTriggerTypeEnum(trigger.getType());
|
||||
if (triggerType == null) {
|
||||
log.warn("[isMatched][未知的触发器类型: {}]", trigger.getType());
|
||||
return false;
|
||||
}
|
||||
|
||||
IotSceneRuleMatcher matcher = triggerMatcherMap.get(triggerType);
|
||||
if (matcher == null) {
|
||||
log.warn("[isMatched][触发器类型({})没有对应的匹配器]", triggerType);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return matcher.isMatched(message, trigger);
|
||||
} catch (Exception e) {
|
||||
log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}, matcher: {}",
|
||||
message, trigger, matcher.getMatcherName(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查子条件是否匹配消息
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public boolean isConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
if (message == null || condition == null || condition.getType() == null) {
|
||||
log.debug("[isConditionMatched][参数无效] message: {}, condition: {}", message, condition);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据条件类型查找对应的匹配器
|
||||
IotSceneRuleConditionTypeEnum conditionType = findConditionTypeEnum(condition.getType());
|
||||
if (conditionType == null) {
|
||||
log.warn("[isConditionMatched][未知的条件类型: {}]", condition.getType());
|
||||
return false;
|
||||
}
|
||||
|
||||
IotSceneRuleMatcher matcher = conditionMatcherMap.get(conditionType);
|
||||
if (matcher == null) {
|
||||
log.warn("[isConditionMatched][条件类型({})没有对应的匹配器]", conditionType);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return matcher.isMatched(message, condition);
|
||||
} catch (Exception e) {
|
||||
log.error("[isConditionMatched][条件匹配异常] message: {}, condition: {}, matcher: {}",
|
||||
message, condition, matcher.getMatcherName(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类型值查找条件类型枚举
|
||||
*
|
||||
* @param typeValue 类型值
|
||||
* @return 条件类型枚举
|
||||
*/
|
||||
private IotSceneRuleConditionTypeEnum findConditionTypeEnum(Integer typeValue) {
|
||||
return Arrays.stream(IotSceneRuleConditionTypeEnum.values())
|
||||
.filter(type -> type.getType().equals(typeValue))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有支持的触发器类型
|
||||
*
|
||||
* @return 支持的触发器类型列表
|
||||
*/
|
||||
public Set<IotSceneRuleTriggerTypeEnum> getSupportedTriggerTypes() {
|
||||
return new HashSet<>(triggerMatcherMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有支持的条件类型
|
||||
*
|
||||
* @return 支持的条件类型列表
|
||||
*/
|
||||
public Set<IotSceneRuleConditionTypeEnum> getSupportedConditionTypes() {
|
||||
return new HashSet<>(conditionMatcherMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定触发器类型的匹配器
|
||||
*
|
||||
* @param triggerType 触发器类型
|
||||
* @return 匹配器实例,如果不存在则返回 null
|
||||
*/
|
||||
public IotSceneRuleMatcher getTriggerMatcher(IotSceneRuleTriggerTypeEnum triggerType) {
|
||||
return triggerMatcherMap.get(triggerType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定条件类型的匹配器
|
||||
*
|
||||
* @param conditionType 条件类型
|
||||
* @return 匹配器实例,如果不存在则返回 null
|
||||
*/
|
||||
public IotSceneRuleMatcher getConditionMatcher(IotSceneRuleConditionTypeEnum conditionType) {
|
||||
return conditionMatcherMap.get(conditionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有匹配器的统计信息
|
||||
*
|
||||
* @return 统计信息映射表
|
||||
*/
|
||||
public Map<String, Object> getMatcherStatistics() {
|
||||
Map<String, Object> statistics = new HashMap<>();
|
||||
statistics.put("totalMatchers", allMatchers.size());
|
||||
statistics.put("triggerMatchers", triggerMatcherMap.size());
|
||||
statistics.put("conditionMatchers", conditionMatcherMap.size());
|
||||
statistics.put("supportedTriggerTypes", getSupportedTriggerTypes());
|
||||
statistics.put("supportedConditionTypes", getSupportedConditionTypes());
|
||||
|
||||
// 触发器匹配器详情
|
||||
Map<String, Object> triggerMatcherDetails = new HashMap<>();
|
||||
triggerMatcherMap.forEach((type, matcher) -> {
|
||||
Map<String, Object> detail = new HashMap<>();
|
||||
detail.put("matcherName", matcher.getMatcherName());
|
||||
detail.put("priority", matcher.getPriority());
|
||||
detail.put("enabled", matcher.isEnabled());
|
||||
triggerMatcherDetails.put(type.name(), detail);
|
||||
});
|
||||
statistics.put("triggerMatcherDetails", triggerMatcherDetails);
|
||||
|
||||
// 条件匹配器详情
|
||||
Map<String, Object> conditionMatcherDetails = new HashMap<>();
|
||||
conditionMatcherMap.forEach((type, matcher) -> {
|
||||
Map<String, Object> detail = new HashMap<>();
|
||||
detail.put("matcherName", matcher.getMatcherName());
|
||||
detail.put("priority", matcher.getPriority());
|
||||
detail.put("enabled", matcher.isEnabled());
|
||||
conditionMatcherDetails.put(type.name(), detail);
|
||||
});
|
||||
statistics.put("conditionMatcherDetails", conditionMatcherDetails);
|
||||
|
||||
return statistics;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 定时触发器匹配器
|
||||
* <p>
|
||||
* 处理定时触发的触发器匹配逻辑
|
||||
* 注意:定时触发器不依赖设备消息,主要用于定时任务场景
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.TRIGGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.TIMER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicTriggerValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查 CRON 表达式是否存在
|
||||
if (StrUtil.isBlank(trigger.getCronExpression())) {
|
||||
logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 定时触发器通常不依赖具体的设备消息
|
||||
// 它是通过定时任务调度器触发的,这里主要是验证配置的有效性
|
||||
|
||||
// 4. 可以添加 CRON 表达式格式验证
|
||||
if (!isValidCronExpression(trigger.getCronExpression())) {
|
||||
logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression());
|
||||
return false;
|
||||
}
|
||||
|
||||
logTriggerMatchSuccess(message, trigger);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 CRON 表达式格式是否有效
|
||||
*
|
||||
* @param cronExpression CRON 表达式
|
||||
* @return 是否有效
|
||||
*/
|
||||
private boolean isValidCronExpression(String cronExpression) {
|
||||
try {
|
||||
// 简单的 CRON 表达式格式验证
|
||||
// 标准 CRON 表达式应该有 6 或 7 个字段(秒 分 时 日 月 周 [年])
|
||||
String[] fields = cronExpression.trim().split("\\s+");
|
||||
return fields.length >= 6 && fields.length <= 7;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 50; // 最低优先级,因为定时触发器不依赖消息
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
// 定时触发器可以根据配置动态启用/禁用
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@ -85,20 +85,21 @@ public class IotDataBridgeExecuteTest extends BaseMockitoUnitTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRedisStreamDataBridge() throws Exception {
|
||||
public void testRedisDataBridge() throws Exception {
|
||||
// 1. 创建执行器实例
|
||||
IotRedisStreamRuleAction action = new IotRedisStreamRuleAction();
|
||||
IotRedisRuleAction action = new IotRedisRuleAction();
|
||||
|
||||
// 2. 创建配置
|
||||
IotDataSinkRedisStreamConfig config = new IotDataSinkRedisStreamConfig()
|
||||
.setHost("127.0.0.1")
|
||||
.setPort(6379)
|
||||
.setDatabase(0)
|
||||
.setPassword("123456")
|
||||
.setTopic("test-stream");
|
||||
// 2. 创建配置 - 测试 Stream 数据结构
|
||||
IotDataSinkRedisConfig config = new IotDataSinkRedisConfig();
|
||||
config.setHost("127.0.0.1");
|
||||
config.setPort(6379);
|
||||
config.setDatabase(0);
|
||||
config.setPassword("123456");
|
||||
config.setTopic("test-stream");
|
||||
config.setDataStructure(1); // Stream 类型
|
||||
|
||||
// 3. 执行测试并验证缓存
|
||||
executeAndVerifyCache(action, config, "RedisStream");
|
||||
executeAndVerifyCache(action, config, "Redis");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.iot.service.rule.scene;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotRuleSceneSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotRuleSceneMapper;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper;
|
||||
import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
@ -24,21 +24,21 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link IotRuleSceneServiceImpl} 的简化单元测试类
|
||||
* {@link IotSceneRuleServiceImpl} 的简化单元测试类
|
||||
* 使用 Mockito 进行纯单元测试,不依赖 Spring 容器
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class IotRuleSceneServiceSimpleTest extends BaseMockitoUnitTest {
|
||||
public class IotSceneRuleServiceSimpleTest extends BaseMockitoUnitTest {
|
||||
|
||||
@InjectMocks
|
||||
private IotRuleSceneServiceImpl ruleSceneService;
|
||||
private IotSceneRuleServiceImpl sceneRuleService;
|
||||
|
||||
@Mock
|
||||
private IotRuleSceneMapper ruleSceneMapper;
|
||||
private IotSceneRuleMapper sceneRuleMapper;
|
||||
|
||||
@Mock
|
||||
private List<IotSceneRuleAction> ruleSceneActions;
|
||||
private List<IotSceneRuleAction> sceneRuleActions;
|
||||
|
||||
@Mock
|
||||
private IotSchedulerManager schedulerManager;
|
||||
@ -50,9 +50,9 @@ public class IotRuleSceneServiceSimpleTest extends BaseMockitoUnitTest {
|
||||
private IotDeviceService deviceService;
|
||||
|
||||
@Test
|
||||
public void testCreateRuleScene_success() {
|
||||
public void testCreateScene_Rule_success() {
|
||||
// 准备参数
|
||||
IotRuleSceneSaveReqVO createReqVO = randomPojo(IotRuleSceneSaveReqVO.class, o -> {
|
||||
IotSceneRuleSaveReqVO createReqVO = randomPojo(IotSceneRuleSaveReqVO.class, o -> {
|
||||
o.setId(null);
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setTriggers(Collections.singletonList(randomPojo(IotSceneRuleDO.Trigger.class)));
|
||||
@ -61,25 +61,25 @@ public class IotRuleSceneServiceSimpleTest extends BaseMockitoUnitTest {
|
||||
|
||||
// Mock 行为
|
||||
Long expectedId = randomLongId();
|
||||
when(ruleSceneMapper.insert(any(IotSceneRuleDO.class))).thenAnswer(invocation -> {
|
||||
IotSceneRuleDO ruleScene = invocation.getArgument(0);
|
||||
ruleScene.setId(expectedId);
|
||||
when(sceneRuleMapper.insert(any(IotSceneRuleDO.class))).thenAnswer(invocation -> {
|
||||
IotSceneRuleDO sceneRule = invocation.getArgument(0);
|
||||
sceneRule.setId(expectedId);
|
||||
return 1;
|
||||
});
|
||||
|
||||
// 调用
|
||||
Long ruleSceneId = ruleSceneService.createRuleScene(createReqVO);
|
||||
Long sceneRuleId = sceneRuleService.createSceneRule(createReqVO);
|
||||
|
||||
// 断言
|
||||
assertEquals(expectedId, ruleSceneId);
|
||||
verify(ruleSceneMapper, times(1)).insert(any(IotSceneRuleDO.class));
|
||||
assertEquals(expectedId, sceneRuleId);
|
||||
verify(sceneRuleMapper, times(1)).insert(any(IotSceneRuleDO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRuleScene_success() {
|
||||
public void testUpdateScene_Rule_success() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
IotRuleSceneSaveReqVO updateReqVO = randomPojo(IotRuleSceneSaveReqVO.class, o -> {
|
||||
IotSceneRuleSaveReqVO updateReqVO = randomPojo(IotSceneRuleSaveReqVO.class, o -> {
|
||||
o.setId(id);
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setTriggers(Collections.singletonList(randomPojo(IotSceneRuleDO.Trigger.class)));
|
||||
@ -87,125 +87,125 @@ public class IotRuleSceneServiceSimpleTest extends BaseMockitoUnitTest {
|
||||
});
|
||||
|
||||
// Mock 行为
|
||||
IotSceneRuleDO existingRuleScene = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
|
||||
when(ruleSceneMapper.selectById(id)).thenReturn(existingRuleScene);
|
||||
when(ruleSceneMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1);
|
||||
IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
|
||||
when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule);
|
||||
when(sceneRuleMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1);
|
||||
|
||||
// 调用
|
||||
assertDoesNotThrow(() -> ruleSceneService.updateRuleScene(updateReqVO));
|
||||
assertDoesNotThrow(() -> sceneRuleService.updateSceneRule(updateReqVO));
|
||||
|
||||
// 验证
|
||||
verify(ruleSceneMapper, times(1)).selectById(id);
|
||||
verify(ruleSceneMapper, times(1)).updateById(any(IotSceneRuleDO.class));
|
||||
verify(sceneRuleMapper, times(1)).selectById(id);
|
||||
verify(sceneRuleMapper, times(1)).updateById(any(IotSceneRuleDO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteRuleScene_success() {
|
||||
public void testDeleteSceneRule_success() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
|
||||
// Mock 行为
|
||||
IotSceneRuleDO existingRuleScene = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
|
||||
when(ruleSceneMapper.selectById(id)).thenReturn(existingRuleScene);
|
||||
when(ruleSceneMapper.deleteById(id)).thenReturn(1);
|
||||
IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
|
||||
when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule);
|
||||
when(sceneRuleMapper.deleteById(id)).thenReturn(1);
|
||||
|
||||
// 调用
|
||||
assertDoesNotThrow(() -> ruleSceneService.deleteRuleScene(id));
|
||||
assertDoesNotThrow(() -> sceneRuleService.deleteSceneRule(id));
|
||||
|
||||
// 验证
|
||||
verify(ruleSceneMapper, times(1)).selectById(id);
|
||||
verify(ruleSceneMapper, times(1)).deleteById(id);
|
||||
verify(sceneRuleMapper, times(1)).selectById(id);
|
||||
verify(sceneRuleMapper, times(1)).deleteById(id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRuleScene() {
|
||||
public void testGetSceneRule() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
IotSceneRuleDO expectedRuleScene = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
|
||||
IotSceneRuleDO expectedSceneRule = randomPojo(IotSceneRuleDO.class, o -> o.setId(id));
|
||||
|
||||
// Mock 行为
|
||||
when(ruleSceneMapper.selectById(id)).thenReturn(expectedRuleScene);
|
||||
when(sceneRuleMapper.selectById(id)).thenReturn(expectedSceneRule);
|
||||
|
||||
// 调用
|
||||
IotSceneRuleDO result = ruleSceneService.getRuleScene(id);
|
||||
IotSceneRuleDO result = sceneRuleService.getSceneRule(id);
|
||||
|
||||
// 断言
|
||||
assertEquals(expectedRuleScene, result);
|
||||
verify(ruleSceneMapper, times(1)).selectById(id);
|
||||
assertEquals(expectedSceneRule, result);
|
||||
verify(sceneRuleMapper, times(1)).selectById(id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRuleSceneStatus_success() {
|
||||
public void testUpdateSceneRuleStatus_success() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
Integer status = CommonStatusEnum.DISABLE.getStatus();
|
||||
|
||||
// Mock 行为
|
||||
IotSceneRuleDO existingRuleScene = randomPojo(IotSceneRuleDO.class, o -> {
|
||||
IotSceneRuleDO existingSceneRule = randomPojo(IotSceneRuleDO.class, o -> {
|
||||
o.setId(id);
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
});
|
||||
when(ruleSceneMapper.selectById(id)).thenReturn(existingRuleScene);
|
||||
when(ruleSceneMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1);
|
||||
when(sceneRuleMapper.selectById(id)).thenReturn(existingSceneRule);
|
||||
when(sceneRuleMapper.updateById(any(IotSceneRuleDO.class))).thenReturn(1);
|
||||
|
||||
// 调用
|
||||
assertDoesNotThrow(() -> ruleSceneService.updateRuleSceneStatus(id, status));
|
||||
assertDoesNotThrow(() -> sceneRuleService.updateSceneRuleStatus(id, status));
|
||||
|
||||
// 验证
|
||||
verify(ruleSceneMapper, times(1)).selectById(id);
|
||||
verify(ruleSceneMapper, times(1)).updateById(any(IotSceneRuleDO.class));
|
||||
verify(sceneRuleMapper, times(1)).selectById(id);
|
||||
verify(sceneRuleMapper, times(1)).updateById(any(IotSceneRuleDO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteRuleSceneByTimer_success() {
|
||||
public void testExecuteSceneRuleByTimer_success() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
|
||||
// Mock 行为
|
||||
IotSceneRuleDO ruleScene = randomPojo(IotSceneRuleDO.class, o -> {
|
||||
IotSceneRuleDO sceneRule = randomPojo(IotSceneRuleDO.class, o -> {
|
||||
o.setId(id);
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
});
|
||||
when(ruleSceneMapper.selectById(id)).thenReturn(ruleScene);
|
||||
when(sceneRuleMapper.selectById(id)).thenReturn(sceneRule);
|
||||
|
||||
// 调用
|
||||
assertDoesNotThrow(() -> ruleSceneService.executeRuleSceneByTimer(id));
|
||||
assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id));
|
||||
|
||||
// 验证
|
||||
verify(ruleSceneMapper, times(1)).selectById(id);
|
||||
verify(sceneRuleMapper, times(1)).selectById(id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteRuleSceneByTimer_notExists() {
|
||||
public void testExecuteSceneRuleByTimer_notExists() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
|
||||
// Mock 行为
|
||||
when(ruleSceneMapper.selectById(id)).thenReturn(null);
|
||||
when(sceneRuleMapper.selectById(id)).thenReturn(null);
|
||||
|
||||
// 调用 - 不存在的场景规则应该不会抛异常,只是记录日志
|
||||
assertDoesNotThrow(() -> ruleSceneService.executeRuleSceneByTimer(id));
|
||||
assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id));
|
||||
|
||||
// 验证
|
||||
verify(ruleSceneMapper, times(1)).selectById(id);
|
||||
verify(sceneRuleMapper, times(1)).selectById(id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteRuleSceneByTimer_disabled() {
|
||||
public void testExecuteSceneRuleByTimer_disabled() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
|
||||
// Mock 行为
|
||||
IotSceneRuleDO ruleScene = randomPojo(IotSceneRuleDO.class, o -> {
|
||||
IotSceneRuleDO sceneRule = randomPojo(IotSceneRuleDO.class, o -> {
|
||||
o.setId(id);
|
||||
o.setStatus(CommonStatusEnum.DISABLE.getStatus());
|
||||
});
|
||||
when(ruleSceneMapper.selectById(id)).thenReturn(ruleScene);
|
||||
when(sceneRuleMapper.selectById(id)).thenReturn(sceneRule);
|
||||
|
||||
// 调用 - 禁用的场景规则应该不会执行,只是记录日志
|
||||
assertDoesNotThrow(() -> ruleSceneService.executeRuleSceneByTimer(id));
|
||||
assertDoesNotThrow(() -> sceneRuleService.executeSceneRuleByTimer(id));
|
||||
|
||||
// 验证
|
||||
verify(ruleSceneMapper, times(1)).selectById(id);
|
||||
verify(sceneRuleMapper, times(1)).selectById(id);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,200 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* IoT 场景规则触发器匹配器测试类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
private IotSceneRuleMatcherManager matcherManager;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// 创建所有匹配器实例
|
||||
List<IotSceneRuleMatcher> matchers = Arrays.asList(
|
||||
new DeviceStateUpdateTriggerMatcher(),
|
||||
new DevicePropertyPostTriggerMatcher(),
|
||||
new DeviceEventPostTriggerMatcher(),
|
||||
new DeviceServiceInvokeTriggerMatcher(),
|
||||
new TimerTriggerMatcher()
|
||||
);
|
||||
|
||||
// 初始化匹配器管理器
|
||||
matcherManager = new IotSceneRuleMatcherManager(matchers);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeviceStateUpdateTriggerMatcher() {
|
||||
// 1. 准备测试数据
|
||||
IotDeviceMessage message = IotDeviceMessage.builder()
|
||||
.requestId("test-001")
|
||||
.method("thing.state.update")
|
||||
.data(1) // 在线状态
|
||||
.build();
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType());
|
||||
trigger.setOperator("=");
|
||||
trigger.setValue("1");
|
||||
|
||||
// 2. 执行测试
|
||||
boolean matched = matcherManager.isMatched(message, trigger);
|
||||
|
||||
// 3. 验证结果
|
||||
assertTrue(matched, "设备状态更新触发器应该匹配");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDevicePropertyPostTriggerMatcher() {
|
||||
// 1. 准备测试数据
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
IotDeviceMessage message = IotDeviceMessage.builder()
|
||||
.requestId("test-002")
|
||||
.method("thing.property.post")
|
||||
.data(25.5) // 温度值
|
||||
.params(params)
|
||||
.build();
|
||||
// 模拟标识符
|
||||
params.put("identifier", "temperature");
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType());
|
||||
trigger.setIdentifier("temperature");
|
||||
trigger.setOperator(">");
|
||||
trigger.setValue("20");
|
||||
|
||||
// 2. 执行测试
|
||||
boolean matched = matcherManager.isMatched(message, trigger);
|
||||
|
||||
// 3. 验证结果
|
||||
assertTrue(matched, "设备属性上报触发器应该匹配");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeviceEventPostTriggerMatcher() {
|
||||
// 1. 准备测试数据
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
IotDeviceMessage message = IotDeviceMessage.builder()
|
||||
.requestId("test-003")
|
||||
.method("thing.event.post")
|
||||
.data("alarm_data")
|
||||
.params(params)
|
||||
.build();
|
||||
// 模拟标识符
|
||||
params.put("identifier", "high_temperature_alarm");
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType());
|
||||
trigger.setIdentifier("high_temperature_alarm");
|
||||
|
||||
// 2. 执行测试
|
||||
boolean matched = matcherManager.isMatched(message, trigger);
|
||||
|
||||
// 3. 验证结果
|
||||
assertTrue(matched, "设备事件上报触发器应该匹配");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeviceServiceInvokeTriggerMatcher() {
|
||||
// 1. 准备测试数据
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
IotDeviceMessage message = IotDeviceMessage.builder()
|
||||
.requestId("test-004")
|
||||
.method("thing.service.invoke")
|
||||
.msg("alarm_data")
|
||||
.params(params)
|
||||
.build();
|
||||
// 模拟标识符
|
||||
params.put("identifier", "restart_device");
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
|
||||
trigger.setIdentifier("restart_device");
|
||||
|
||||
// 2. 执行测试
|
||||
boolean matched = matcherManager.isMatched(message, trigger);
|
||||
|
||||
// 3. 验证结果
|
||||
assertTrue(matched, "设备服务调用触发器应该匹配");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTimerTriggerMatcher() {
|
||||
// 1. 准备测试数据
|
||||
IotDeviceMessage message = IotDeviceMessage.builder()
|
||||
.requestId("test-005")
|
||||
.method("timer.trigger") // 定时触发器不依赖具体消息方法
|
||||
.build();
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
|
||||
trigger.setCronExpression("0 0 12 * * ?"); // 每天中午12点
|
||||
|
||||
// 2. 执行测试
|
||||
boolean matched = matcherManager.isMatched(message, trigger);
|
||||
|
||||
// 3. 验证结果
|
||||
assertTrue(matched, "定时触发器应该匹配");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidTriggerType() {
|
||||
// 1. 准备测试数据
|
||||
IotDeviceMessage message = IotDeviceMessage.builder()
|
||||
.requestId("test-006")
|
||||
.method("unknown.method")
|
||||
.build();
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(999); // 无效的触发器类型
|
||||
|
||||
// 2. 执行测试
|
||||
boolean matched = matcherManager.isMatched(message, trigger);
|
||||
|
||||
// 3. 验证结果
|
||||
assertFalse(matched, "无效的触发器类型应该不匹配");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMatcherManagerStatistics() {
|
||||
// 1. 执行测试
|
||||
var statistics = matcherManager.getMatcherStatistics();
|
||||
|
||||
// 2. 验证结果
|
||||
assertNotNull(statistics);
|
||||
assertEquals(5, statistics.get("totalMatchers"));
|
||||
assertEquals(5, statistics.get("enabledMatchers"));
|
||||
assertNotNull(statistics.get("supportedTriggerTypes"));
|
||||
assertNotNull(statistics.get("matcherDetails"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSupportedTriggerTypes() {
|
||||
// 1. 执行测试
|
||||
var supportedTypes = matcherManager.getSupportedTriggerTypes();
|
||||
|
||||
// 2. 验证结果
|
||||
assertNotNull(supportedTypes);
|
||||
assertEquals(5, supportedTypes.size());
|
||||
assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE));
|
||||
assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST));
|
||||
assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST));
|
||||
assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE));
|
||||
assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.TIMER));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,22 +1,10 @@
|
||||
-- TODO @puhui999:sql 格式
|
||||
-- IoT 模块测试数据清理脚本
|
||||
DELETE
|
||||
FROM "iot_scene_rule";
|
||||
DELETE
|
||||
FROM "iot_product";
|
||||
DELETE
|
||||
FROM "iot_device";
|
||||
DELETE
|
||||
FROM "iot_thing_model";
|
||||
DELETE
|
||||
FROM "iot_device_data";
|
||||
DELETE
|
||||
FROM "iot_alert_config";
|
||||
DELETE
|
||||
FROM "iot_alert_record";
|
||||
DELETE
|
||||
FROM "iot_ota_firmware";
|
||||
DELETE
|
||||
FROM "iot_ota_task";
|
||||
DELETE
|
||||
FROM "iot_ota_record";
|
||||
DELETE FROM "iot_scene_rule";
|
||||
DELETE FROM "iot_product";
|
||||
DELETE FROM "iot_device";
|
||||
DELETE FROM "iot_thing_model";
|
||||
DELETE FROM "iot_device_data";
|
||||
DELETE FROM "iot_alert_config";
|
||||
DELETE FROM "iot_alert_record";
|
||||
DELETE FROM "iot_ota_firmware";
|
||||
DELETE FROM "iot_ota_task";
|
||||
DELETE FROM "iot_ota_record";
|
||||
|
||||
@ -1,300 +1,115 @@
|
||||
-- TODO @puhui999:sql 格式
|
||||
-- IoT 模块测试数据库表结构
|
||||
-- 基于 H2 数据库语法,兼容 MySQL 模式
|
||||
|
||||
-- IoT 场景联动规则表
|
||||
CREATE TABLE IF NOT EXISTS "iot_scene_rule"
|
||||
(
|
||||
"id"
|
||||
bigint
|
||||
NOT
|
||||
NULL
|
||||
GENERATED
|
||||
BY
|
||||
DEFAULT AS
|
||||
IDENTITY,
|
||||
"name"
|
||||
varchar
|
||||
(
|
||||
255
|
||||
) NOT NULL DEFAULT '',
|
||||
"description" varchar
|
||||
(
|
||||
500
|
||||
) DEFAULT NULL,
|
||||
CREATE TABLE IF NOT EXISTS "iot_scene_rule" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"name" varchar(255) NOT NULL DEFAULT '',
|
||||
"description" varchar(500) DEFAULT NULL,
|
||||
"status" tinyint NOT NULL DEFAULT '0',
|
||||
"triggers" text,
|
||||
"actions" text,
|
||||
"creator" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"creator" varchar(64) DEFAULT '',
|
||||
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updater" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"updater" varchar(64) DEFAULT '',
|
||||
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
"tenant_id" bigint NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY
|
||||
(
|
||||
"id"
|
||||
)
|
||||
) COMMENT 'IoT 场景联动规则表';
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT 'IoT 场景联动规则表';
|
||||
|
||||
-- IoT 产品表
|
||||
CREATE TABLE IF NOT EXISTS "iot_product"
|
||||
(
|
||||
"id"
|
||||
bigint
|
||||
NOT
|
||||
NULL
|
||||
GENERATED
|
||||
BY
|
||||
DEFAULT AS
|
||||
IDENTITY,
|
||||
"name"
|
||||
varchar
|
||||
(
|
||||
255
|
||||
) NOT NULL DEFAULT '',
|
||||
"product_key" varchar
|
||||
(
|
||||
100
|
||||
) NOT NULL DEFAULT '',
|
||||
CREATE TABLE IF NOT EXISTS "iot_product" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"name" varchar(255) NOT NULL DEFAULT '',
|
||||
"product_key" varchar(100) NOT NULL DEFAULT '',
|
||||
"protocol_type" tinyint NOT NULL DEFAULT '0',
|
||||
"category_id" bigint DEFAULT NULL,
|
||||
"description" varchar
|
||||
(
|
||||
500
|
||||
) DEFAULT NULL,
|
||||
"description" varchar(500) DEFAULT NULL,
|
||||
"data_format" tinyint NOT NULL DEFAULT '0',
|
||||
"device_type" tinyint NOT NULL DEFAULT '0',
|
||||
"net_type" tinyint NOT NULL DEFAULT '0',
|
||||
"validate_type" tinyint NOT NULL DEFAULT '0',
|
||||
"status" tinyint NOT NULL DEFAULT '0',
|
||||
"creator" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"creator" varchar(64) DEFAULT '',
|
||||
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updater" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"updater" varchar(64) DEFAULT '',
|
||||
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
"tenant_id" bigint NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY
|
||||
(
|
||||
"id"
|
||||
)
|
||||
) COMMENT 'IoT 产品表';
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT 'IoT 产品表';
|
||||
|
||||
-- IoT 设备表
|
||||
CREATE TABLE IF NOT EXISTS "iot_device"
|
||||
(
|
||||
"id"
|
||||
bigint
|
||||
NOT
|
||||
NULL
|
||||
GENERATED
|
||||
BY
|
||||
DEFAULT AS
|
||||
IDENTITY,
|
||||
"device_name"
|
||||
varchar
|
||||
(
|
||||
255
|
||||
) NOT NULL DEFAULT '',
|
||||
CREATE TABLE IF NOT EXISTS "iot_device" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"device_name" varchar(255) NOT NULL DEFAULT '',
|
||||
"product_id" bigint NOT NULL,
|
||||
"device_key" varchar
|
||||
(
|
||||
100
|
||||
) NOT NULL DEFAULT '',
|
||||
"device_secret" varchar
|
||||
(
|
||||
100
|
||||
) NOT NULL DEFAULT '',
|
||||
"nickname" varchar
|
||||
(
|
||||
255
|
||||
) DEFAULT NULL,
|
||||
"device_key" varchar(100) NOT NULL DEFAULT '',
|
||||
"device_secret" varchar(100) NOT NULL DEFAULT '',
|
||||
"nickname" varchar(255) DEFAULT NULL,
|
||||
"status" tinyint NOT NULL DEFAULT '0',
|
||||
"status_last_update_time" timestamp DEFAULT NULL,
|
||||
"last_online_time" timestamp DEFAULT NULL,
|
||||
"last_offline_time" timestamp DEFAULT NULL,
|
||||
"active_time" timestamp DEFAULT NULL,
|
||||
"ip" varchar
|
||||
(
|
||||
50
|
||||
) DEFAULT NULL,
|
||||
"firmware_version" varchar
|
||||
(
|
||||
50
|
||||
) DEFAULT NULL,
|
||||
"ip" varchar(50) DEFAULT NULL,
|
||||
"firmware_version" varchar(50) DEFAULT NULL,
|
||||
"device_type" tinyint NOT NULL DEFAULT '0',
|
||||
"gateway_id" bigint DEFAULT NULL,
|
||||
"sub_device_count" int NOT NULL DEFAULT '0',
|
||||
"creator" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"creator" varchar(64) DEFAULT '',
|
||||
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updater" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"updater" varchar(64) DEFAULT '',
|
||||
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
"tenant_id" bigint NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY
|
||||
(
|
||||
"id"
|
||||
)
|
||||
) COMMENT 'IoT 设备表';
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT 'IoT 设备表';
|
||||
|
||||
-- IoT 物模型表
|
||||
CREATE TABLE IF NOT EXISTS "iot_thing_model"
|
||||
(
|
||||
"id"
|
||||
bigint
|
||||
NOT
|
||||
NULL
|
||||
GENERATED
|
||||
BY
|
||||
DEFAULT AS
|
||||
IDENTITY,
|
||||
"product_id"
|
||||
bigint
|
||||
NOT
|
||||
NULL,
|
||||
"identifier"
|
||||
varchar
|
||||
(
|
||||
100
|
||||
) NOT NULL DEFAULT '',
|
||||
"name" varchar
|
||||
(
|
||||
255
|
||||
) NOT NULL DEFAULT '',
|
||||
"description" varchar
|
||||
(
|
||||
500
|
||||
) DEFAULT NULL,
|
||||
CREATE TABLE IF NOT EXISTS "iot_thing_model" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"product_id" bigint NOT NULL,
|
||||
"identifier" varchar(100) NOT NULL DEFAULT '',
|
||||
"name" varchar(255) NOT NULL DEFAULT '',
|
||||
"description" varchar(500) DEFAULT NULL,
|
||||
"type" tinyint NOT NULL DEFAULT '1',
|
||||
"property" text,
|
||||
"creator" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"creator" varchar(64) DEFAULT '',
|
||||
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updater" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"updater" varchar(64) DEFAULT '',
|
||||
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
"tenant_id" bigint NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY
|
||||
(
|
||||
"id"
|
||||
)
|
||||
) COMMENT 'IoT 物模型表';
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT 'IoT 物模型表';
|
||||
|
||||
-- IoT 设备数据表
|
||||
CREATE TABLE IF NOT EXISTS "iot_device_data"
|
||||
(
|
||||
"id"
|
||||
bigint
|
||||
NOT
|
||||
NULL
|
||||
GENERATED
|
||||
BY
|
||||
DEFAULT AS
|
||||
IDENTITY,
|
||||
"device_id"
|
||||
bigint
|
||||
NOT
|
||||
NULL,
|
||||
"product_id"
|
||||
bigint
|
||||
NOT
|
||||
NULL,
|
||||
"identifier"
|
||||
varchar
|
||||
(
|
||||
100
|
||||
) NOT NULL DEFAULT '',
|
||||
CREATE TABLE IF NOT EXISTS "iot_device_data" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"device_id" bigint NOT NULL,
|
||||
"product_id" bigint NOT NULL,
|
||||
"identifier" varchar(100) NOT NULL DEFAULT '',
|
||||
"type" tinyint NOT NULL DEFAULT '1',
|
||||
"data" text,
|
||||
"ts" bigint NOT NULL DEFAULT '0',
|
||||
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY
|
||||
(
|
||||
"id"
|
||||
)
|
||||
) COMMENT 'IoT 设备数据表';
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT 'IoT 设备数据表';
|
||||
|
||||
-- IoT 告警配置表
|
||||
CREATE TABLE IF NOT EXISTS "iot_alert_config"
|
||||
(
|
||||
"id"
|
||||
bigint
|
||||
NOT
|
||||
NULL
|
||||
GENERATED
|
||||
BY
|
||||
DEFAULT AS
|
||||
IDENTITY,
|
||||
"name"
|
||||
varchar
|
||||
(
|
||||
255
|
||||
) NOT NULL DEFAULT '',
|
||||
CREATE TABLE IF NOT EXISTS "iot_alert_config" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"name" varchar(255) NOT NULL DEFAULT '',
|
||||
"product_id" bigint NOT NULL,
|
||||
"device_id" bigint DEFAULT NULL,
|
||||
"rule_id" bigint DEFAULT NULL,
|
||||
"status" tinyint NOT NULL DEFAULT '0',
|
||||
"creator" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"creator" varchar(64) DEFAULT '',
|
||||
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updater" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"updater" varchar(64) DEFAULT '',
|
||||
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
"tenant_id" bigint NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY
|
||||
(
|
||||
"id"
|
||||
)
|
||||
) COMMENT 'IoT 告警配置表';
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT 'IoT 告警配置表';
|
||||
|
||||
-- IoT 告警记录表
|
||||
CREATE TABLE IF NOT EXISTS "iot_alert_record"
|
||||
(
|
||||
"id"
|
||||
bigint
|
||||
NOT
|
||||
NULL
|
||||
GENERATED
|
||||
BY
|
||||
DEFAULT AS
|
||||
IDENTITY,
|
||||
"alert_config_id"
|
||||
bigint
|
||||
NOT
|
||||
NULL,
|
||||
"alert_name"
|
||||
varchar
|
||||
(
|
||||
255
|
||||
) NOT NULL DEFAULT '',
|
||||
CREATE TABLE IF NOT EXISTS "iot_alert_record" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"alert_config_id" bigint NOT NULL,
|
||||
"alert_name" varchar(255) NOT NULL DEFAULT '',
|
||||
"product_id" bigint NOT NULL,
|
||||
"device_id" bigint DEFAULT NULL,
|
||||
"rule_id" bigint DEFAULT NULL,
|
||||
@ -303,171 +118,65 @@ CREATE TABLE IF NOT EXISTS "iot_alert_record"
|
||||
"deal_status" tinyint NOT NULL DEFAULT '0',
|
||||
"deal_time" timestamp DEFAULT NULL,
|
||||
"deal_user_id" bigint DEFAULT NULL,
|
||||
"deal_remark" varchar
|
||||
(
|
||||
500
|
||||
) DEFAULT NULL,
|
||||
"creator" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"deal_remark" varchar(500) DEFAULT NULL,
|
||||
"creator" varchar(64) DEFAULT '',
|
||||
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updater" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"updater" varchar(64) DEFAULT '',
|
||||
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
"tenant_id" bigint NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY
|
||||
(
|
||||
"id"
|
||||
)
|
||||
) COMMENT 'IoT 告警记录表';
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT 'IoT 告警记录表';
|
||||
|
||||
-- IoT OTA 固件表
|
||||
CREATE TABLE IF NOT EXISTS "iot_ota_firmware"
|
||||
(
|
||||
"id"
|
||||
bigint
|
||||
NOT
|
||||
NULL
|
||||
GENERATED
|
||||
BY
|
||||
DEFAULT AS
|
||||
IDENTITY,
|
||||
"name"
|
||||
varchar
|
||||
(
|
||||
255
|
||||
) NOT NULL DEFAULT '',
|
||||
CREATE TABLE IF NOT EXISTS "iot_ota_firmware" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"name" varchar(255) NOT NULL DEFAULT '',
|
||||
"product_id" bigint NOT NULL,
|
||||
"version" varchar
|
||||
(
|
||||
50
|
||||
) NOT NULL DEFAULT '',
|
||||
"description" varchar
|
||||
(
|
||||
500
|
||||
) DEFAULT NULL,
|
||||
"file_url" varchar
|
||||
(
|
||||
500
|
||||
) DEFAULT NULL,
|
||||
"version" varchar(50) NOT NULL DEFAULT '',
|
||||
"description" varchar(500) DEFAULT NULL,
|
||||
"file_url" varchar(500) DEFAULT NULL,
|
||||
"file_size" bigint NOT NULL DEFAULT '0',
|
||||
"status" tinyint NOT NULL DEFAULT '0',
|
||||
"creator" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"creator" varchar(64) DEFAULT '',
|
||||
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updater" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"updater" varchar(64) DEFAULT '',
|
||||
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
"tenant_id" bigint NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY
|
||||
(
|
||||
"id"
|
||||
)
|
||||
) COMMENT 'IoT OTA 固件表';
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT 'IoT OTA 固件表';
|
||||
|
||||
-- IoT OTA 升级任务表
|
||||
CREATE TABLE IF NOT EXISTS "iot_ota_task"
|
||||
(
|
||||
"id"
|
||||
bigint
|
||||
NOT
|
||||
NULL
|
||||
GENERATED
|
||||
BY
|
||||
DEFAULT AS
|
||||
IDENTITY,
|
||||
"name"
|
||||
varchar
|
||||
(
|
||||
255
|
||||
) NOT NULL DEFAULT '',
|
||||
CREATE TABLE IF NOT EXISTS "iot_ota_task" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"name" varchar(255) NOT NULL DEFAULT '',
|
||||
"firmware_id" bigint NOT NULL,
|
||||
"product_id" bigint NOT NULL,
|
||||
"upgrade_type" tinyint NOT NULL DEFAULT '0',
|
||||
"status" tinyint NOT NULL DEFAULT '0',
|
||||
"creator" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"creator" varchar(64) DEFAULT '',
|
||||
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updater" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"updater" varchar(64) DEFAULT '',
|
||||
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
"tenant_id" bigint NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY
|
||||
(
|
||||
"id"
|
||||
)
|
||||
) COMMENT 'IoT OTA 升级任务表';
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT 'IoT OTA 升级任务表';
|
||||
|
||||
-- IoT OTA 升级记录表
|
||||
CREATE TABLE IF NOT EXISTS "iot_ota_record"
|
||||
(
|
||||
"id"
|
||||
bigint
|
||||
NOT
|
||||
NULL
|
||||
GENERATED
|
||||
BY
|
||||
DEFAULT AS
|
||||
IDENTITY,
|
||||
"task_id"
|
||||
bigint
|
||||
NOT
|
||||
NULL,
|
||||
"firmware_id"
|
||||
bigint
|
||||
NOT
|
||||
NULL,
|
||||
"device_id"
|
||||
bigint
|
||||
NOT
|
||||
NULL,
|
||||
"status"
|
||||
tinyint
|
||||
NOT
|
||||
NULL
|
||||
DEFAULT
|
||||
'0',
|
||||
"progress"
|
||||
int
|
||||
NOT
|
||||
NULL
|
||||
DEFAULT
|
||||
'0',
|
||||
"error_msg"
|
||||
varchar
|
||||
(
|
||||
500
|
||||
) DEFAULT NULL,
|
||||
CREATE TABLE IF NOT EXISTS "iot_ota_record" (
|
||||
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
"task_id" bigint NOT NULL,
|
||||
"firmware_id" bigint NOT NULL,
|
||||
"device_id" bigint NOT NULL,
|
||||
"status" tinyint NOT NULL DEFAULT '0',
|
||||
"progress" int NOT NULL DEFAULT '0',
|
||||
"error_msg" varchar(500) DEFAULT NULL,
|
||||
"start_time" timestamp DEFAULT NULL,
|
||||
"end_time" timestamp DEFAULT NULL,
|
||||
"creator" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"creator" varchar(64) DEFAULT '',
|
||||
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updater" varchar
|
||||
(
|
||||
64
|
||||
) DEFAULT '',
|
||||
"updater" varchar(64) DEFAULT '',
|
||||
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
"tenant_id" bigint NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY
|
||||
(
|
||||
"id"
|
||||
)
|
||||
) COMMENT 'IoT OTA 升级记录表';
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT 'IoT OTA 升级记录表';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user