Commit 48cfcdadc13a642f28859912c7e808f0f9d80302
1 parent
4f84182d
【代码优化】IoT:实现规则 IotRuleSceneDeviceControlAction 执行器
Showing
17 changed files
with
306 additions
and
115 deletions
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneActionTypeEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.enums.rule; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.core.ArrayValuable; | ||
| 4 | +import lombok.Getter; | ||
| 5 | +import lombok.RequiredArgsConstructor; | ||
| 6 | + | ||
| 7 | +import java.util.Arrays; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * Iot 规则场景的触发类型枚举 | ||
| 11 | + * | ||
| 12 | + * 设备触发,定时触发 | ||
| 13 | + */ | ||
| 14 | +@RequiredArgsConstructor | ||
| 15 | +@Getter | ||
| 16 | +public enum IotRuleSceneActionTypeEnum implements ArrayValuable<Integer> { | ||
| 17 | + | ||
| 18 | + DEVICE_CONTROL(1), // 设备执行 | ||
| 19 | + ALERT(2), // 告警执行 | ||
| 20 | + DATA_BRIDGE(3); // 桥接执行 | ||
| 21 | + | ||
| 22 | + private final Integer type; | ||
| 23 | + | ||
| 24 | + public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneActionTypeEnum::getType).toArray(Integer[]::new); | ||
| 25 | + | ||
| 26 | + @Override | ||
| 27 | + public Integer[] array() { | ||
| 28 | + return ARRAYS; | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | +} |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.device; | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.device; | ||
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | import cn.iocoder.yudao.framework.mybatis.core.type.LongSetTypeHandler; | 4 | import cn.iocoder.yudao.framework.mybatis.core.type.LongSetTypeHandler; |
| 5 | +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; | ||
| 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; |
| 6 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum; | 7 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum; |
| 7 | import com.baomidou.mybatisplus.annotation.KeySequence; | 8 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| @@ -27,7 +28,7 @@ import java.util.Set; | @@ -27,7 +28,7 @@ import java.util.Set; | ||
| 27 | @Builder | 28 | @Builder |
| 28 | @NoArgsConstructor | 29 | @NoArgsConstructor |
| 29 | @AllArgsConstructor | 30 | @AllArgsConstructor |
| 30 | -public class IotDeviceDO extends BaseDO { | 31 | +public class IotDeviceDO extends TenantBaseDO { |
| 31 | 32 | ||
| 32 | /** | 33 | /** |
| 33 | * 设备 ID,主键,自增 | 34 | * 设备 ID,主键,自增 |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotRuleSceneDO.java
| 1 | package cn.iocoder.yudao.module.iot.dal.dataobject.rule; | 1 | package cn.iocoder.yudao.module.iot.dal.dataobject.rule; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | 3 | +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; |
| 4 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | 4 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; | 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; |
| 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; |
| 7 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum; | 7 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum; |
| 8 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum; | 8 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum; |
| 9 | +import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum; | ||
| 9 | import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerConditionParameterOperatorEnum; | 10 | import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerConditionParameterOperatorEnum; |
| 11 | +import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum; | ||
| 10 | import com.baomidou.mybatisplus.annotation.KeySequence; | 12 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 11 | import com.baomidou.mybatisplus.annotation.TableField; | 13 | import com.baomidou.mybatisplus.annotation.TableField; |
| 12 | import com.baomidou.mybatisplus.annotation.TableId; | 14 | import com.baomidou.mybatisplus.annotation.TableId; |
| @@ -30,7 +32,7 @@ import java.util.Map; | @@ -30,7 +32,7 @@ import java.util.Map; | ||
| 30 | @Builder | 32 | @Builder |
| 31 | @NoArgsConstructor | 33 | @NoArgsConstructor |
| 32 | @AllArgsConstructor | 34 | @AllArgsConstructor |
| 33 | -public class IotRuleSceneDO extends BaseDO { | 35 | +public class IotRuleSceneDO extends TenantBaseDO { |
| 34 | 36 | ||
| 35 | /** | 37 | /** |
| 36 | * 场景编号 | 38 | * 场景编号 |
| @@ -56,24 +58,26 @@ public class IotRuleSceneDO extends BaseDO { | @@ -56,24 +58,26 @@ public class IotRuleSceneDO extends BaseDO { | ||
| 56 | * 触发器数组 | 58 | * 触发器数组 |
| 57 | */ | 59 | */ |
| 58 | @TableField(typeHandler = JacksonTypeHandler.class) | 60 | @TableField(typeHandler = JacksonTypeHandler.class) |
| 59 | - private List<Trigger> triggers; | 61 | + private List<TriggerConfig> triggers; |
| 62 | + | ||
| 63 | + // TODO @芋艿:需要调研下 https://help.aliyun.com/zh/iot/user-guide/scene-orchestration-1?spm=a2c4g.11186623.help-menu-30520.d_2_4_5_0.45413908fxCSVa | ||
| 60 | 64 | ||
| 61 | /** | 65 | /** |
| 62 | * 执行器数组 | 66 | * 执行器数组 |
| 63 | */ | 67 | */ |
| 64 | @TableField(typeHandler = JacksonTypeHandler.class) | 68 | @TableField(typeHandler = JacksonTypeHandler.class) |
| 65 | - private List<Actuator> actuators; | 69 | + private List<ActionConfig> actions; |
| 66 | 70 | ||
| 67 | /** | 71 | /** |
| 68 | - * 触发器 | 72 | + * 触发器配置 |
| 69 | */ | 73 | */ |
| 70 | @Data | 74 | @Data |
| 71 | - public static class Trigger { | 75 | + public static class TriggerConfig { |
| 72 | 76 | ||
| 73 | /** | 77 | /** |
| 74 | * 触发类型 | 78 | * 触发类型 |
| 75 | * | 79 | * |
| 76 | - * 枚举 {@link cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum} | 80 | + * 枚举 {@link IotRuleSceneTriggerTypeEnum} |
| 77 | */ | 81 | */ |
| 78 | private Integer type; | 82 | private Integer type; |
| 79 | 83 | ||
| @@ -93,14 +97,15 @@ public class IotRuleSceneDO extends BaseDO { | @@ -93,14 +97,15 @@ public class IotRuleSceneDO extends BaseDO { | ||
| 93 | /** | 97 | /** |
| 94 | * 触发条件数组 | 98 | * 触发条件数组 |
| 95 | * | 99 | * |
| 96 | - * TODO @芋艿:注释说明 | 100 | + * 当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#DEVICE} 时,必填 |
| 101 | + * 条件与条件之间,是“或”的关系 | ||
| 97 | */ | 102 | */ |
| 98 | private List<TriggerCondition> conditions; | 103 | private List<TriggerCondition> conditions; |
| 99 | 104 | ||
| 100 | /** | 105 | /** |
| 101 | * CRON 表达式 | 106 | * CRON 表达式 |
| 102 | * | 107 | * |
| 103 | - * TODO @芋艿:注释说明 | 108 | + * 当 {@link #type} 为 {@link IotRuleSceneTriggerTypeEnum#TIMER} 时,必填 |
| 104 | */ | 109 | */ |
| 105 | private String cronExpression; | 110 | private String cronExpression; |
| 106 | 111 | ||
| @@ -127,6 +132,8 @@ public class IotRuleSceneDO extends BaseDO { | @@ -127,6 +132,8 @@ public class IotRuleSceneDO extends BaseDO { | ||
| 127 | 132 | ||
| 128 | /** | 133 | /** |
| 129 | * 参数数组 | 134 | * 参数数组 |
| 135 | + * | ||
| 136 | + * 参数与参数之间,是“或”的关系 | ||
| 130 | */ | 137 | */ |
| 131 | private List<TriggerConditionParameter> parameters; | 138 | private List<TriggerConditionParameter> parameters; |
| 132 | 139 | ||
| @@ -163,60 +170,60 @@ public class IotRuleSceneDO extends BaseDO { | @@ -163,60 +170,60 @@ public class IotRuleSceneDO extends BaseDO { | ||
| 163 | } | 170 | } |
| 164 | 171 | ||
| 165 | /** | 172 | /** |
| 166 | - * 执行器 | 173 | + * 执行器配置 |
| 167 | */ | 174 | */ |
| 168 | @Data | 175 | @Data |
| 169 | - public static class Actuator { | 176 | + public static class ActionConfig { |
| 170 | 177 | ||
| 171 | /** | 178 | /** |
| 172 | * 执行类型 | 179 | * 执行类型 |
| 173 | * | 180 | * |
| 174 | - * TODO @芋艿:control、alert、webhook(待定) | 181 | + * 枚举 {@link IotRuleSceneActionTypeEnum} |
| 175 | */ | 182 | */ |
| 176 | private Integer type; | 183 | private Integer type; |
| 177 | 184 | ||
| 178 | /** | 185 | /** |
| 179 | - * 产品标识 | ||
| 180 | - * | ||
| 181 | - * 关联 {@link IotProductDO#getProductKey()} | ||
| 182 | - */ | ||
| 183 | - private String productKey; | ||
| 184 | - /** | ||
| 185 | - * 设备名称数组 | ||
| 186 | - * | ||
| 187 | - * 关联 {@link IotDeviceDO#getDeviceName()} | ||
| 188 | - */ | ||
| 189 | - private List<String> deviceNames; | ||
| 190 | - | ||
| 191 | - /** | ||
| 192 | - * 控制数组 | 186 | + * 设备控制 |
| 193 | * | 187 | * |
| 194 | - * TODO 芋艿:类型的情况下 | 188 | + * 当 {@link #type} 为 {@link IotRuleSceneActionTypeEnum#DEVICE_CONTROL} 时,必填 |
| 195 | */ | 189 | */ |
| 196 | - private List<ActuatorControl> controls; | 190 | + private ActionDeviceControl deviceControl; |
| 197 | 191 | ||
| 198 | /** | 192 | /** |
| 199 | * 数据桥接编号 | 193 | * 数据桥接编号 |
| 200 | * | 194 | * |
| 201 | - * TODO 芋艿:暂定! | 195 | + * 当 {@link #type} 为 {@link IotRuleSceneActionTypeEnum#DATA_BRIDGE} 时,必填 |
| 202 | * TODO 芋艿:关联 | 196 | * TODO 芋艿:关联 |
| 203 | */ | 197 | */ |
| 204 | - private Long bridgeId; | 198 | + private Long dataBridgeId; |
| 205 | 199 | ||
| 206 | } | 200 | } |
| 207 | 201 | ||
| 208 | /** | 202 | /** |
| 209 | - * 执行器控制 | 203 | + * 执行设备控制 |
| 210 | */ | 204 | */ |
| 211 | @Data | 205 | @Data |
| 212 | - public static class ActuatorControl { | 206 | + public static class ActionDeviceControl { |
| 207 | + | ||
| 208 | + /** | ||
| 209 | + * 产品标识 | ||
| 210 | + * | ||
| 211 | + * 关联 {@link IotProductDO#getProductKey()} | ||
| 212 | + */ | ||
| 213 | + private String productKey; | ||
| 214 | + /** | ||
| 215 | + * 设备名称数组 | ||
| 216 | + * | ||
| 217 | + * 关联 {@link IotDeviceDO#getDeviceName()} | ||
| 218 | + */ | ||
| 219 | + private List<String> deviceNames; | ||
| 213 | 220 | ||
| 214 | /** | 221 | /** |
| 215 | * 消息类型 | 222 | * 消息类型 |
| 216 | * | 223 | * |
| 217 | * 枚举 {@link IotDeviceMessageTypeEnum#PROPERTY}、{@link IotDeviceMessageTypeEnum#SERVICE} | 224 | * 枚举 {@link IotDeviceMessageTypeEnum#PROPERTY}、{@link IotDeviceMessageTypeEnum#SERVICE} |
| 218 | */ | 225 | */ |
| 219 | - private Integer type; | 226 | + private String type; |
| 220 | /** | 227 | /** |
| 221 | * 消息标识符 | 228 | * 消息标识符 |
| 222 | * | 229 | * |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java
| @@ -17,7 +17,7 @@ public interface RedisKeyConstants { | @@ -17,7 +17,7 @@ public interface RedisKeyConstants { | ||
| 17 | * HASH KEY:identifier 属性标识 | 17 | * HASH KEY:identifier 属性标识 |
| 18 | * VALUE 数据类型:String(JSON) {@link IotDevicePropertyDO} | 18 | * VALUE 数据类型:String(JSON) {@link IotDevicePropertyDO} |
| 19 | */ | 19 | */ |
| 20 | - String DEVICE_PROPERTY = "device_property:%s"; | 20 | + String DEVICE_PROPERTY = "iot:device_property:%s"; |
| 21 | 21 | ||
| 22 | /** | 22 | /** |
| 23 | * 设备的最后上报时间,采用 ZSET 结构 | 23 | * 设备的最后上报时间,采用 ZSET 结构 |
| @@ -25,23 +25,23 @@ public interface RedisKeyConstants { | @@ -25,23 +25,23 @@ public interface RedisKeyConstants { | ||
| 25 | * KEY 格式:{deviceKey} | 25 | * KEY 格式:{deviceKey} |
| 26 | * SCORE:上报时间 | 26 | * SCORE:上报时间 |
| 27 | */ | 27 | */ |
| 28 | - String DEVICE_REPORT_TIMES = "device_report_times"; | 28 | + String DEVICE_REPORT_TIMES = "iot:device_report_times"; |
| 29 | 29 | ||
| 30 | /** | 30 | /** |
| 31 | - * 设备信息的数据缓存,使用 Spring Cache 操作 | 31 | + * 设备信息的数据缓存,使用 Spring Cache 操作(忽略租户) |
| 32 | * | 32 | * |
| 33 | * KEY 格式:device_${productKey}_${deviceKey} | 33 | * KEY 格式:device_${productKey}_${deviceKey} |
| 34 | * VALUE 数据类型:String(JSON) | 34 | * VALUE 数据类型:String(JSON) |
| 35 | */ | 35 | */ |
| 36 | - String DEVICE = "device"; | 36 | + String DEVICE = "iot:device"; |
| 37 | 37 | ||
| 38 | /** | 38 | /** |
| 39 | - * 物模型的数据缓存,使用 Spring Cache 操作 | 39 | + * 物模型的数据缓存,使用 Spring Cache 操作(忽略租户) |
| 40 | * | 40 | * |
| 41 | * KEY 格式:thing_model_${productKey} | 41 | * KEY 格式:thing_model_${productKey} |
| 42 | * VALUE 数据类型:String 数组(JSON),即 {@link cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO} 列表 | 42 | * VALUE 数据类型:String 数组(JSON),即 {@link cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO} 列表 |
| 43 | */ | 43 | */ |
| 44 | - String THING_MODEL_LIST = "thing_model_list"; | 44 | + String THING_MODEL_LIST = "iot:thing_model_list"; |
| 45 | 45 | ||
| 46 | /** | 46 | /** |
| 47 | * 设备插件的插件进程编号的映射,采用 HASH 结构 | 47 | * 设备插件的插件进程编号的映射,采用 HASH 结构 |
| @@ -50,6 +50,6 @@ public interface RedisKeyConstants { | @@ -50,6 +50,6 @@ public interface RedisKeyConstants { | ||
| 50 | * HASH KEY:${deviceKey} | 50 | * HASH KEY:${deviceKey} |
| 51 | * VALUE:插件进程编号,对应 {@link IotPluginInstanceDO#getProcessId()} 字段 | 51 | * VALUE:插件进程编号,对应 {@link IotPluginInstanceDO#getProcessId()} 字段 |
| 52 | */ | 52 | */ |
| 53 | - String DEVICE_PLUGIN_INSTANCE_PROCESS_IDS = "device_plugin_instance_process_ids"; | 53 | + String DEVICE_PLUGIN_INSTANCE_PROCESS_IDS = "iot:device_plugin_instance_process_ids"; |
| 54 | 54 | ||
| 55 | } | 55 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotRuleSceneMessageHandler.java
| @@ -24,7 +24,7 @@ public class IotRuleSceneMessageHandler { | @@ -24,7 +24,7 @@ public class IotRuleSceneMessageHandler { | ||
| 24 | @Async | 24 | @Async |
| 25 | public void onMessage(IotDeviceMessage message) { | 25 | public void onMessage(IotDeviceMessage message) { |
| 26 | log.info("[onMessage][消息内容({})]", message); | 26 | log.info("[onMessage][消息内容({})]", message); |
| 27 | - ruleSceneService.executeRuleScene(message); | 27 | + ruleSceneService.executeRuleSceneByDevice(message); |
| 28 | } | 28 | } |
| 29 | 29 | ||
| 30 | } | 30 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/package-info.java deleted
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/message/IotDeviceMessage.java
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
| @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException; | @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException; | ||
| 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 11 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | 11 | import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; |
| 12 | +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; | ||
| 12 | import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | 13 | import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; |
| 13 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; | 14 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; |
| 14 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | 15 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| @@ -261,13 +262,9 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -261,13 +262,9 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 261 | } | 262 | } |
| 262 | 263 | ||
| 263 | @Override | 264 | @Override |
| 264 | - public IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) { | ||
| 265 | - // 保证在 @CacheEvict 之前,忽略租户 | ||
| 266 | - return TenantUtils.executeIgnore(() -> getSelf().getDeviceByProductKeyAndDeviceNameFromCache0(productKey, deviceName)); | ||
| 267 | - } | ||
| 268 | - | ||
| 269 | @Cacheable(value = RedisKeyConstants.DEVICE, key = "#productKey + '_' + #deviceName", unless = "#result == null") | 265 | @Cacheable(value = RedisKeyConstants.DEVICE, key = "#productKey + '_' + #deviceName", unless = "#result == null") |
| 270 | - public IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache0(String productKey, String deviceName) { | 266 | + @TenantIgnore // 忽略租户信息,跨租户 productKey + deviceName 是唯一的 |
| 267 | + public IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) { | ||
| 271 | return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName); | 268 | return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName); |
| 272 | } | 269 | } |
| 273 | 270 | ||
| @@ -389,8 +386,8 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -389,8 +386,8 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 389 | } | 386 | } |
| 390 | 387 | ||
| 391 | private void deleteDeviceCache(IotDeviceDO device) { | 388 | private void deleteDeviceCache(IotDeviceDO device) { |
| 392 | - // 保证在 @CacheEvict 之前,忽略租户 | ||
| 393 | - TenantUtils.executeIgnore(() -> getSelf().deleteDeviceCache0(device)); | 389 | + // 保证 Spring AOP 触发 |
| 390 | + getSelf().deleteDeviceCache0(device); | ||
| 394 | } | 391 | } |
| 395 | 392 | ||
| 396 | private void deleteDeviceCache(List<IotDeviceDO> devices) { | 393 | private void deleteDeviceCache(List<IotDeviceDO> devices) { |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceDownstreamService.java
| 1 | package cn.iocoder.yudao.module.iot.service.device.control; | 1 | package cn.iocoder.yudao.module.iot.service.device.control; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceDownstreamReqVO; | 3 | import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceDownstreamReqVO; |
| 4 | +import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; | ||
| 4 | import jakarta.validation.Valid; | 5 | import jakarta.validation.Valid; |
| 5 | 6 | ||
| 6 | /** | 7 | /** |
| @@ -16,7 +17,8 @@ public interface IotDeviceDownstreamService { | @@ -16,7 +17,8 @@ public interface IotDeviceDownstreamService { | ||
| 16 | * 设备下行,可用于设备模拟 | 17 | * 设备下行,可用于设备模拟 |
| 17 | * | 18 | * |
| 18 | * @param downstreamReqVO 设备下行请求 VO | 19 | * @param downstreamReqVO 设备下行请求 VO |
| 20 | + * @return 下发消息 | ||
| 19 | */ | 21 | */ |
| 20 | - void downstreamDevice(@Valid IotDeviceDownstreamReqVO downstreamReqVO); | 22 | + IotDeviceMessage downstreamDevice(@Valid IotDeviceDownstreamReqVO downstreamReqVO); |
| 21 | 23 | ||
| 22 | } | 24 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceDownstreamServiceImpl.java
| @@ -54,7 +54,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | @@ -54,7 +54,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | ||
| 54 | private IotDeviceProducer deviceProducer; | 54 | private IotDeviceProducer deviceProducer; |
| 55 | 55 | ||
| 56 | @Override | 56 | @Override |
| 57 | - public void downstreamDevice(IotDeviceDownstreamReqVO downstreamReqVO) { | 57 | + public IotDeviceMessage downstreamDevice(IotDeviceDownstreamReqVO downstreamReqVO) { |
| 58 | // 校验设备是否存在 | 58 | // 校验设备是否存在 |
| 59 | IotDeviceDO device = deviceService.validateDeviceExists(downstreamReqVO.getId()); | 59 | IotDeviceDO device = deviceService.validateDeviceExists(downstreamReqVO.getId()); |
| 60 | // TODO 芋艿:父设备的处理 | 60 | // TODO 芋艿:父设备的处理 |
| @@ -62,31 +62,28 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | @@ -62,31 +62,28 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | ||
| 62 | 62 | ||
| 63 | // 服务调用 | 63 | // 服务调用 |
| 64 | if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.SERVICE.getType())) { | 64 | if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.SERVICE.getType())) { |
| 65 | - invokeDeviceService(downstreamReqVO, device, parentDevice); | ||
| 66 | - return; | 65 | + return invokeDeviceService(downstreamReqVO, device, parentDevice); |
| 67 | } | 66 | } |
| 68 | // 属性相关 | 67 | // 属性相关 |
| 69 | if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.PROPERTY.getType())) { | 68 | if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.PROPERTY.getType())) { |
| 70 | // 属性设置 | 69 | // 属性设置 |
| 71 | if (Objects.equals(downstreamReqVO.getIdentifier(), | 70 | if (Objects.equals(downstreamReqVO.getIdentifier(), |
| 72 | IotDeviceMessageIdentifierEnum.PROPERTY_SET.getIdentifier())) { | 71 | IotDeviceMessageIdentifierEnum.PROPERTY_SET.getIdentifier())) { |
| 73 | - setDeviceProperty(downstreamReqVO, device, parentDevice); | ||
| 74 | - return; | 72 | + return setDeviceProperty(downstreamReqVO, device, parentDevice); |
| 75 | } | 73 | } |
| 76 | // 属性设置 | 74 | // 属性设置 |
| 77 | if (Objects.equals(downstreamReqVO.getIdentifier(), | 75 | if (Objects.equals(downstreamReqVO.getIdentifier(), |
| 78 | IotDeviceMessageIdentifierEnum.PROPERTY_GET.getIdentifier())) { | 76 | IotDeviceMessageIdentifierEnum.PROPERTY_GET.getIdentifier())) { |
| 79 | - getDeviceProperty(downstreamReqVO, device, parentDevice); | ||
| 80 | - return; | 77 | + return getDeviceProperty(downstreamReqVO, device, parentDevice); |
| 81 | } | 78 | } |
| 82 | } | 79 | } |
| 83 | // 配置下发 | 80 | // 配置下发 |
| 84 | if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.CONFIG.getType()) | 81 | if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.CONFIG.getType()) |
| 85 | && Objects.equals(downstreamReqVO.getIdentifier(), IotDeviceMessageIdentifierEnum.CONFIG_SET.getIdentifier())) { | 82 | && Objects.equals(downstreamReqVO.getIdentifier(), IotDeviceMessageIdentifierEnum.CONFIG_SET.getIdentifier())) { |
| 86 | - setDeviceConfig(downstreamReqVO, device, parentDevice); | ||
| 87 | - return; | 83 | + return setDeviceConfig(downstreamReqVO, device, parentDevice); |
| 88 | } | 84 | } |
| 89 | // TODO 芋艿:ota 升级 | 85 | // TODO 芋艿:ota 升级 |
| 86 | + return null; | ||
| 90 | } | 87 | } |
| 91 | 88 | ||
| 92 | /** | 89 | /** |
| @@ -95,9 +92,10 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | @@ -95,9 +92,10 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | ||
| 95 | * @param downstreamReqVO 下行请求 | 92 | * @param downstreamReqVO 下行请求 |
| 96 | * @param device 设备 | 93 | * @param device 设备 |
| 97 | * @param parentDevice 父设备 | 94 | * @param parentDevice 父设备 |
| 95 | + * @return 下发消息 | ||
| 98 | */ | 96 | */ |
| 99 | @SuppressWarnings("unchecked") | 97 | @SuppressWarnings("unchecked") |
| 100 | - private void invokeDeviceService(IotDeviceDownstreamReqVO downstreamReqVO, | 98 | + private IotDeviceMessage invokeDeviceService(IotDeviceDownstreamReqVO downstreamReqVO, |
| 101 | IotDeviceDO device, IotDeviceDO parentDevice) { | 99 | IotDeviceDO device, IotDeviceDO parentDevice) { |
| 102 | // 1. 参数校验 | 100 | // 1. 参数校验 |
| 103 | if (!(downstreamReqVO.getData() instanceof Map<?, ?>)) { | 101 | if (!(downstreamReqVO.getData() instanceof Map<?, ?>)) { |
| @@ -124,6 +122,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | @@ -124,6 +122,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | ||
| 124 | device.getDeviceKey(), reqDTO, result); | 122 | device.getDeviceKey(), reqDTO, result); |
| 125 | throw exception(DEVICE_DOWNSTREAM_FAILED, result.getMsg()); | 123 | throw exception(DEVICE_DOWNSTREAM_FAILED, result.getMsg()); |
| 126 | } | 124 | } |
| 125 | + return message; | ||
| 127 | } | 126 | } |
| 128 | 127 | ||
| 129 | /** | 128 | /** |
| @@ -132,10 +131,11 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | @@ -132,10 +131,11 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | ||
| 132 | * @param downstreamReqVO 下行请求 | 131 | * @param downstreamReqVO 下行请求 |
| 133 | * @param device 设备 | 132 | * @param device 设备 |
| 134 | * @param parentDevice 父设备 | 133 | * @param parentDevice 父设备 |
| 134 | + * @return 下发消息 | ||
| 135 | */ | 135 | */ |
| 136 | @SuppressWarnings("unchecked") | 136 | @SuppressWarnings("unchecked") |
| 137 | - private void setDeviceProperty(IotDeviceDownstreamReqVO downstreamReqVO, | ||
| 138 | - IotDeviceDO device, IotDeviceDO parentDevice) { | 137 | + private IotDeviceMessage setDeviceProperty(IotDeviceDownstreamReqVO downstreamReqVO, |
| 138 | + IotDeviceDO device, IotDeviceDO parentDevice) { | ||
| 139 | // 1. 参数校验 | 139 | // 1. 参数校验 |
| 140 | if (!(downstreamReqVO.getData() instanceof Map<?, ?>)) { | 140 | if (!(downstreamReqVO.getData() instanceof Map<?, ?>)) { |
| 141 | throw new ServiceException(BAD_REQUEST.getCode(), "data 不是 Map 类型"); | 141 | throw new ServiceException(BAD_REQUEST.getCode(), "data 不是 Map 类型"); |
| @@ -162,6 +162,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | @@ -162,6 +162,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | ||
| 162 | device.getDeviceKey(), reqDTO, result); | 162 | device.getDeviceKey(), reqDTO, result); |
| 163 | throw exception(DEVICE_DOWNSTREAM_FAILED, result.getMsg()); | 163 | throw exception(DEVICE_DOWNSTREAM_FAILED, result.getMsg()); |
| 164 | } | 164 | } |
| 165 | + return message; | ||
| 165 | } | 166 | } |
| 166 | 167 | ||
| 167 | /** | 168 | /** |
| @@ -170,10 +171,11 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | @@ -170,10 +171,11 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | ||
| 170 | * @param downstreamReqVO 下行请求 | 171 | * @param downstreamReqVO 下行请求 |
| 171 | * @param device 设备 | 172 | * @param device 设备 |
| 172 | * @param parentDevice 父设备 | 173 | * @param parentDevice 父设备 |
| 174 | + * @return 下发消息 | ||
| 173 | */ | 175 | */ |
| 174 | @SuppressWarnings("unchecked") | 176 | @SuppressWarnings("unchecked") |
| 175 | - private void getDeviceProperty(IotDeviceDownstreamReqVO downstreamReqVO, | ||
| 176 | - IotDeviceDO device, IotDeviceDO parentDevice) { | 177 | + private IotDeviceMessage getDeviceProperty(IotDeviceDownstreamReqVO downstreamReqVO, |
| 178 | + IotDeviceDO device, IotDeviceDO parentDevice) { | ||
| 177 | // 1. 参数校验 | 179 | // 1. 参数校验 |
| 178 | if (!(downstreamReqVO.getData() instanceof List<?>)) { | 180 | if (!(downstreamReqVO.getData() instanceof List<?>)) { |
| 179 | throw new ServiceException(BAD_REQUEST.getCode(), "data 不是 List 类型"); | 181 | throw new ServiceException(BAD_REQUEST.getCode(), "data 不是 List 类型"); |
| @@ -200,6 +202,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | @@ -200,6 +202,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | ||
| 200 | device.getDeviceKey(), reqDTO, result); | 202 | device.getDeviceKey(), reqDTO, result); |
| 201 | throw exception(DEVICE_DOWNSTREAM_FAILED, result.getMsg()); | 203 | throw exception(DEVICE_DOWNSTREAM_FAILED, result.getMsg()); |
| 202 | } | 204 | } |
| 205 | + return message; | ||
| 203 | } | 206 | } |
| 204 | 207 | ||
| 205 | /** | 208 | /** |
| @@ -208,10 +211,11 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | @@ -208,10 +211,11 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | ||
| 208 | * @param downstreamReqVO 下行请求 | 211 | * @param downstreamReqVO 下行请求 |
| 209 | * @param device 设备 | 212 | * @param device 设备 |
| 210 | * @param parentDevice 父设备 | 213 | * @param parentDevice 父设备 |
| 214 | + * @return 下发消息 | ||
| 211 | */ | 215 | */ |
| 212 | @SuppressWarnings({"unchecked", "unused"}) | 216 | @SuppressWarnings({"unchecked", "unused"}) |
| 213 | - private void setDeviceConfig(IotDeviceDownstreamReqVO downstreamReqVO, | ||
| 214 | - IotDeviceDO device, IotDeviceDO parentDevice) { | 217 | + private IotDeviceMessage setDeviceConfig(IotDeviceDownstreamReqVO downstreamReqVO, |
| 218 | + IotDeviceDO device, IotDeviceDO parentDevice) { | ||
| 215 | // 1. 参数转换,无需校验 | 219 | // 1. 参数转换,无需校验 |
| 216 | Map<String, Object> config = JsonUtils.parseObject(device.getConfig(), Map.class); | 220 | Map<String, Object> config = JsonUtils.parseObject(device.getConfig(), Map.class); |
| 217 | 221 | ||
| @@ -235,6 +239,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | @@ -235,6 +239,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | ||
| 235 | device.getDeviceKey(), reqDTO, result); | 239 | device.getDeviceKey(), reqDTO, result); |
| 236 | throw exception(DEVICE_DOWNSTREAM_FAILED, result.getMsg()); | 240 | throw exception(DEVICE_DOWNSTREAM_FAILED, result.getMsg()); |
| 237 | } | 241 | } |
| 242 | + return message; | ||
| 238 | } | 243 | } |
| 239 | 244 | ||
| 240 | /** | 245 | /** |
| @@ -275,7 +280,8 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | @@ -275,7 +280,8 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic | ||
| 275 | private void sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device, Integer code) { | 280 | private void sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device, Integer code) { |
| 276 | // 1. 完善消息 | 281 | // 1. 完善消息 |
| 277 | message.setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()) | 282 | message.setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()) |
| 278 | - .setDeviceKey(device.getDeviceKey()); | 283 | + .setDeviceKey(device.getDeviceKey()) |
| 284 | + .setTenantId(device.getTenantId()); | ||
| 279 | Assert.notNull(message.getRequestId(), "requestId 不能为空"); | 285 | Assert.notNull(message.getRequestId(), "requestId 不能为空"); |
| 280 | if (message.getReportTime() == null) { | 286 | if (message.getReportTime() == null) { |
| 281 | message.setReportTime(LocalDateTime.now()); | 287 | message.setReportTime(LocalDateTime.now()); |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java
| @@ -98,26 +98,27 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { | @@ -98,26 +98,27 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { | ||
| 98 | updateReqDTO.getProductKey(), updateReqDTO.getDeviceName()); | 98 | updateReqDTO.getProductKey(), updateReqDTO.getDeviceName()); |
| 99 | return; | 99 | return; |
| 100 | } | 100 | } |
| 101 | - // 1.2 记录设备的最后时间 | ||
| 102 | - updateDeviceLastTime(device, updateReqDTO); | ||
| 103 | - // 1.3 当前状态一致,不处理 | ||
| 104 | - if (Objects.equals(device.getState(), updateReqDTO.getState())) { | ||
| 105 | - return; | ||
| 106 | - } | ||
| 107 | - | ||
| 108 | - // 2. 更新设备状态 | ||
| 109 | - TenantUtils.executeIgnore(() -> | ||
| 110 | - deviceService.updateDeviceState(device.getId(), updateReqDTO.getState())); | ||
| 111 | - | ||
| 112 | - // 3. TODO 芋艿:子设备的关联 | ||
| 113 | - | ||
| 114 | - // 4. 发送设备消息 | ||
| 115 | - IotDeviceMessage message = BeanUtils.toBean(updateReqDTO, IotDeviceMessage.class) | ||
| 116 | - .setType(IotDeviceMessageTypeEnum.STATE.getType()) | ||
| 117 | - .setIdentifier(ObjUtil.equals(updateReqDTO.getState(), IotDeviceStateEnum.ONLINE.getState()) | ||
| 118 | - ? IotDeviceMessageIdentifierEnum.STATE_ONLINE.getIdentifier() | ||
| 119 | - : IotDeviceMessageIdentifierEnum.STATE_OFFLINE.getIdentifier()); | ||
| 120 | - sendDeviceMessage(message, device); | 101 | + TenantUtils.execute(device.getTenantId(), () -> { |
| 102 | + // 1.2 记录设备的最后时间 | ||
| 103 | + updateDeviceLastTime(device, updateReqDTO); | ||
| 104 | + // 1.3 当前状态一致,不处理 | ||
| 105 | + if (Objects.equals(device.getState(), updateReqDTO.getState())) { | ||
| 106 | + return; | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + // 2. 更新设备状态 | ||
| 110 | + deviceService.updateDeviceState(device.getId(), updateReqDTO.getState()); | ||
| 111 | + | ||
| 112 | + // 3. TODO 芋艿:子设备的关联 | ||
| 113 | + | ||
| 114 | + // 4. 发送设备消息 | ||
| 115 | + IotDeviceMessage message = BeanUtils.toBean(updateReqDTO, IotDeviceMessage.class) | ||
| 116 | + .setType(IotDeviceMessageTypeEnum.STATE.getType()) | ||
| 117 | + .setIdentifier(ObjUtil.equals(updateReqDTO.getState(), IotDeviceStateEnum.ONLINE.getState()) | ||
| 118 | + ? IotDeviceMessageIdentifierEnum.STATE_ONLINE.getIdentifier() | ||
| 119 | + : IotDeviceMessageIdentifierEnum.STATE_OFFLINE.getIdentifier()); | ||
| 120 | + sendDeviceMessage(message, device); | ||
| 121 | + }); | ||
| 121 | } | 122 | } |
| 122 | 123 | ||
| 123 | @Override | 124 | @Override |
| @@ -174,7 +175,8 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { | @@ -174,7 +175,8 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { | ||
| 174 | 175 | ||
| 175 | private void sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device) { | 176 | private void sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device) { |
| 176 | // 1. 完善消息 | 177 | // 1. 完善消息 |
| 177 | - message.setDeviceKey(device.getDeviceKey()); | 178 | + message.setDeviceKey(device.getDeviceKey()) |
| 179 | + .setTenantId(device.getTenantId()); | ||
| 178 | if (StrUtil.isEmpty(message.getRequestId())) { | 180 | if (StrUtil.isEmpty(message.getRequestId())) { |
| 179 | message.setRequestId(IdUtil.fastSimpleUUID()); | 181 | message.setRequestId(IdUtil.fastSimpleUUID()); |
| 180 | } | 182 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/IotRuleSceneService.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule; | 1 | package cn.iocoder.yudao.module.iot.service.rule; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO; | 3 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO; |
| 4 | +import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum; | ||
| 4 | import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; | 5 | import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; |
| 5 | 6 | ||
| 6 | import java.util.List; | 7 | import java.util.List; |
| @@ -22,10 +23,12 @@ public interface IotRuleSceneService { | @@ -22,10 +23,12 @@ public interface IotRuleSceneService { | ||
| 22 | List<IotRuleSceneDO> getRuleSceneListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName); | 23 | List<IotRuleSceneDO> getRuleSceneListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName); |
| 23 | 24 | ||
| 24 | /** | 25 | /** |
| 25 | - * 执行规则场景 | 26 | + * 基于 {@link IotRuleSceneTriggerTypeEnum#DEVICE} 场景,执行规则场景 |
| 26 | * | 27 | * |
| 27 | * @param message 消息 | 28 | * @param message 消息 |
| 28 | */ | 29 | */ |
| 29 | - void executeRuleScene(IotDeviceMessage message); | 30 | + void executeRuleSceneByDevice(IotDeviceMessage message); |
| 31 | + | ||
| 32 | + // TODO @芋艿:基于 timer 场景,执行规则场景 | ||
| 30 | 33 | ||
| 31 | } | 34 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/IotRuleSceneServiceImpl.java
| @@ -11,13 +11,16 @@ import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | @@ -11,13 +11,16 @@ import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||
| 11 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | 11 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| 12 | import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; | 12 | import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; |
| 13 | import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; | 13 | import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; |
| 14 | +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | ||
| 14 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO; | 15 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO; |
| 15 | import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotRuleSceneMapper; | 16 | import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotRuleSceneMapper; |
| 16 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum; | 17 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum; |
| 17 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum; | 18 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum; |
| 19 | +import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum; | ||
| 18 | import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerConditionParameterOperatorEnum; | 20 | import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerConditionParameterOperatorEnum; |
| 19 | import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum; | 21 | import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum; |
| 20 | import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; | 22 | import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; |
| 23 | +import cn.iocoder.yudao.module.iot.service.rule.action.IotRuleSceneAction; | ||
| 21 | import jakarta.annotation.Resource; | 24 | import jakarta.annotation.Resource; |
| 22 | import lombok.extern.slf4j.Slf4j; | 25 | import lombok.extern.slf4j.Slf4j; |
| 23 | import org.springframework.stereotype.Service; | 26 | import org.springframework.stereotype.Service; |
| @@ -43,6 +46,9 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | @@ -43,6 +46,9 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | ||
| 43 | @Resource | 46 | @Resource |
| 44 | private IotRuleSceneMapper ruleSceneMapper; | 47 | private IotRuleSceneMapper ruleSceneMapper; |
| 45 | 48 | ||
| 49 | + @Resource | ||
| 50 | + private List<IotRuleSceneAction> ruleSceneActions; | ||
| 51 | + | ||
| 46 | // TODO 芋艿,缓存待实现 | 52 | // TODO 芋艿,缓存待实现 |
| 47 | @Override | 53 | @Override |
| 48 | @TenantIgnore // 忽略租户隔离:因为 IotRuleSceneMessageHandler 调用时,一般未传递租户,所以需要忽略 | 54 | @TenantIgnore // 忽略租户隔离:因为 IotRuleSceneMessageHandler 调用时,一般未传递租户,所以需要忽略 |
| @@ -50,7 +56,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | @@ -50,7 +56,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | ||
| 50 | if (true) { | 56 | if (true) { |
| 51 | IotRuleSceneDO ruleScene01 = new IotRuleSceneDO(); | 57 | IotRuleSceneDO ruleScene01 = new IotRuleSceneDO(); |
| 52 | ruleScene01.setTriggers(CollUtil.newArrayList()); | 58 | ruleScene01.setTriggers(CollUtil.newArrayList()); |
| 53 | - IotRuleSceneDO.Trigger trigger01 = new IotRuleSceneDO.Trigger(); | 59 | + IotRuleSceneDO.TriggerConfig trigger01 = new IotRuleSceneDO.TriggerConfig(); |
| 54 | trigger01.setType(IotRuleSceneTriggerTypeEnum.DEVICE.getType()); | 60 | trigger01.setType(IotRuleSceneTriggerTypeEnum.DEVICE.getType()); |
| 55 | trigger01.setConditions(CollUtil.newArrayList()); | 61 | trigger01.setConditions(CollUtil.newArrayList()); |
| 56 | // 属性 | 62 | // 属性 |
| @@ -120,7 +126,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | @@ -120,7 +126,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | ||
| 120 | condition02.setIdentifier(IotDeviceMessageIdentifierEnum.STATE_ONLINE.getIdentifier()); | 126 | condition02.setIdentifier(IotDeviceMessageIdentifierEnum.STATE_ONLINE.getIdentifier()); |
| 121 | condition02.setParameters(CollUtil.newArrayList()); | 127 | condition02.setParameters(CollUtil.newArrayList()); |
| 122 | trigger01.getConditions().add(condition02); | 128 | trigger01.getConditions().add(condition02); |
| 123 | - // TODO 芋艿:事件 | 129 | + // 事件 |
| 124 | IotRuleSceneDO.TriggerCondition condition03 = new IotRuleSceneDO.TriggerCondition(); | 130 | IotRuleSceneDO.TriggerCondition condition03 = new IotRuleSceneDO.TriggerCondition(); |
| 125 | condition03.setType(IotDeviceMessageTypeEnum.EVENT.getType()); | 131 | condition03.setType(IotDeviceMessageTypeEnum.EVENT.getType()); |
| 126 | condition03.setIdentifier("xxx"); | 132 | condition03.setIdentifier("xxx"); |
| @@ -131,13 +137,28 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | @@ -131,13 +137,28 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | ||
| 131 | parameter030.setValue("1"); | 137 | parameter030.setValue("1"); |
| 132 | trigger01.getConditions().add(condition03); | 138 | trigger01.getConditions().add(condition03); |
| 133 | ruleScene01.getTriggers().add(trigger01); | 139 | ruleScene01.getTriggers().add(trigger01); |
| 140 | + // 动作 | ||
| 141 | + ruleScene01.setActions(CollUtil.newArrayList()); | ||
| 142 | + IotRuleSceneDO.ActionConfig action01 = new IotRuleSceneDO.ActionConfig(); | ||
| 143 | + action01.setType(IotRuleSceneActionTypeEnum.DEVICE_CONTROL.getType()); | ||
| 144 | + IotRuleSceneDO.ActionDeviceControl actionDeviceControl01 = new IotRuleSceneDO.ActionDeviceControl(); | ||
| 145 | + actionDeviceControl01.setProductKey("4aymZgOTOOCrDKRT"); | ||
| 146 | + actionDeviceControl01.setDeviceNames(ListUtil.of("small")); | ||
| 147 | + actionDeviceControl01.setType(IotDeviceMessageTypeEnum.PROPERTY.getType()); | ||
| 148 | + actionDeviceControl01.setIdentifier(IotDeviceMessageIdentifierEnum.PROPERTY_SET.getIdentifier()); | ||
| 149 | + actionDeviceControl01.setData(MapUtil.<String, Object>builder() | ||
| 150 | + .put("power", 1) | ||
| 151 | + .put("color", "red") | ||
| 152 | + .build()); | ||
| 153 | + action01.setDeviceControl(actionDeviceControl01); | ||
| 154 | + ruleScene01.getActions().add(action01); | ||
| 134 | return ListUtil.toList(ruleScene01); | 155 | return ListUtil.toList(ruleScene01); |
| 135 | } | 156 | } |
| 136 | 157 | ||
| 137 | List<IotRuleSceneDO> list = ruleSceneMapper.selectList(); | 158 | List<IotRuleSceneDO> list = ruleSceneMapper.selectList(); |
| 138 | // TODO @芋艿:需要考虑开启状态 | 159 | // TODO @芋艿:需要考虑开启状态 |
| 139 | return filterList(list, ruleScene -> { | 160 | return filterList(list, ruleScene -> { |
| 140 | - for (IotRuleSceneDO.Trigger trigger : ruleScene.getTriggers()) { | 161 | + for (IotRuleSceneDO.TriggerConfig trigger : ruleScene.getTriggers()) { |
| 141 | if (ObjUtil.notEqual(trigger.getProductKey(), productKey)) { | 162 | if (ObjUtil.notEqual(trigger.getProductKey(), productKey)) { |
| 142 | continue; | 163 | continue; |
| 143 | } | 164 | } |
| @@ -151,12 +172,17 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | @@ -151,12 +172,17 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | ||
| 151 | } | 172 | } |
| 152 | 173 | ||
| 153 | @Override | 174 | @Override |
| 154 | - public void executeRuleScene(IotDeviceMessage message) { | ||
| 155 | - // 1. 获得设备匹配的规则场景 | ||
| 156 | - List<IotRuleSceneDO> ruleScenes = getMatchedRuleSceneList(message); | ||
| 157 | - if (CollUtil.isEmpty(ruleScenes)) { | ||
| 158 | - return; | ||
| 159 | - } | 175 | + public void executeRuleSceneByDevice(IotDeviceMessage message) { |
| 176 | + TenantUtils.execute(message.getTenantId(), () -> { | ||
| 177 | + // 1. 获得设备匹配的规则场景 | ||
| 178 | + List<IotRuleSceneDO> ruleScenes = getMatchedRuleSceneList(message); | ||
| 179 | + if (CollUtil.isEmpty(ruleScenes)) { | ||
| 180 | + return; | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + // 2. 执行规则场景 | ||
| 184 | + executeRuleSceneAction(message, ruleScenes); | ||
| 185 | + }); | ||
| 160 | } | 186 | } |
| 161 | 187 | ||
| 162 | /** | 188 | /** |
| @@ -176,7 +202,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | @@ -176,7 +202,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | ||
| 176 | 202 | ||
| 177 | // 2. 匹配 trigger 触发器的条件 | 203 | // 2. 匹配 trigger 触发器的条件 |
| 178 | return filterList(ruleScenes, ruleScene -> { | 204 | return filterList(ruleScenes, ruleScene -> { |
| 179 | - for (IotRuleSceneDO.Trigger trigger : ruleScene.getTriggers()) { | 205 | + for (IotRuleSceneDO.TriggerConfig trigger : ruleScene.getTriggers()) { |
| 180 | // 2.1 非设备触发,不匹配 | 206 | // 2.1 非设备触发,不匹配 |
| 181 | if (ObjUtil.notEqual(trigger.getType(), IotRuleSceneTriggerTypeEnum.DEVICE.getType())) { | 207 | if (ObjUtil.notEqual(trigger.getType(), IotRuleSceneTriggerTypeEnum.DEVICE.getType())) { |
| 182 | return false; | 208 | return false; |
| @@ -219,7 +245,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | @@ -219,7 +245,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | ||
| 219 | */ | 245 | */ |
| 220 | @SuppressWarnings({"unchecked", "DataFlowIssue"}) | 246 | @SuppressWarnings({"unchecked", "DataFlowIssue"}) |
| 221 | private boolean isTriggerConditionParameterMatched(IotDeviceMessage message, IotRuleSceneDO.TriggerConditionParameter parameter, | 247 | private boolean isTriggerConditionParameterMatched(IotDeviceMessage message, IotRuleSceneDO.TriggerConditionParameter parameter, |
| 222 | - IotRuleSceneDO ruleScene, IotRuleSceneDO.Trigger trigger) { | 248 | + IotRuleSceneDO ruleScene, IotRuleSceneDO.TriggerConfig trigger) { |
| 223 | // 1.1 校验操作符是否合法 | 249 | // 1.1 校验操作符是否合法 |
| 224 | IotRuleSceneTriggerConditionParameterOperatorEnum operator = | 250 | IotRuleSceneTriggerConditionParameterOperatorEnum operator = |
| 225 | IotRuleSceneTriggerConditionParameterOperatorEnum.operatorOf(parameter.getOperator()); | 251 | IotRuleSceneTriggerConditionParameterOperatorEnum.operatorOf(parameter.getOperator()); |
| @@ -266,4 +292,36 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | @@ -266,4 +292,36 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService { | ||
| 266 | } | 292 | } |
| 267 | } | 293 | } |
| 268 | 294 | ||
| 295 | + /** | ||
| 296 | + * 执行规则场景的动作 | ||
| 297 | + * | ||
| 298 | + * @param message 设备消息 | ||
| 299 | + * @param ruleScenes 规则场景列表 | ||
| 300 | + */ | ||
| 301 | + private void executeRuleSceneAction(IotDeviceMessage message, List<IotRuleSceneDO> ruleScenes) { | ||
| 302 | + // 1. 遍历规则场景 | ||
| 303 | + ruleScenes.forEach(ruleScene -> { | ||
| 304 | + // 2. 遍历规则场景的动作 | ||
| 305 | + ruleScene.getActions().forEach(actionConfig -> { | ||
| 306 | + // 3.1 获取对应的动作 Action 数组 | ||
| 307 | + List<IotRuleSceneAction> actions = filterList(ruleSceneActions, | ||
| 308 | + action -> action.getType().getType().equals(actionConfig.getType())); | ||
| 309 | + if (CollUtil.isEmpty(actions)) { | ||
| 310 | + return; | ||
| 311 | + } | ||
| 312 | + // 3.2 执行动作 | ||
| 313 | + actions.forEach(action -> { | ||
| 314 | + try { | ||
| 315 | + action.execute(message, actionConfig); | ||
| 316 | + log.info("[executeRuleSceneAction][消息({}) 规则场景编号({}) 的执行动作({}) 成功]", | ||
| 317 | + message, ruleScene.getId(), actionConfig); | ||
| 318 | + } catch (Exception e) { | ||
| 319 | + log.error("[executeRuleSceneAction][消息({}) 规则场景编号({}) 的执行动作({}) 执行异常]", | ||
| 320 | + message, ruleScene.getId(), actionConfig, e); | ||
| 321 | + } | ||
| 322 | + }); | ||
| 323 | + }); | ||
| 324 | + }); | ||
| 325 | + } | ||
| 326 | + | ||
| 269 | } | 327 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/IotRuleSceneAction.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.action; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO; | ||
| 4 | +import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum; | ||
| 5 | +import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * IOT 规则场景的场景执行器接口 | ||
| 9 | + * | ||
| 10 | + * @author 芋道源码 | ||
| 11 | + */ | ||
| 12 | +public interface IotRuleSceneAction { | ||
| 13 | + | ||
| 14 | + /** | ||
| 15 | + * 执行场景 | ||
| 16 | + * | ||
| 17 | + * @param message 消息 | ||
| 18 | + * @param config 配置 | ||
| 19 | + */ | ||
| 20 | + void execute(IotDeviceMessage message, IotRuleSceneDO.ActionConfig config); | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 获得类型 | ||
| 24 | + * | ||
| 25 | + * @return 类型 | ||
| 26 | + */ | ||
| 27 | + IotRuleSceneActionTypeEnum getType(); | ||
| 28 | + | ||
| 29 | +} |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/IotRuleSceneDeviceControlAction.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.action; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.lang.Assert; | ||
| 4 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceDownstreamReqVO; | ||
| 5 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | ||
| 6 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO; | ||
| 7 | +import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum; | ||
| 8 | +import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; | ||
| 9 | +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; | ||
| 10 | +import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceDownstreamService; | ||
| 11 | +import jakarta.annotation.Resource; | ||
| 12 | +import lombok.extern.slf4j.Slf4j; | ||
| 13 | +import org.springframework.stereotype.Component; | ||
| 14 | + | ||
| 15 | +/** | ||
| 16 | + * IoT 设备控制的 {@link IotRuleSceneAction} 实现类 | ||
| 17 | + * | ||
| 18 | + * @author 芋道源码 | ||
| 19 | + */ | ||
| 20 | +@Component | ||
| 21 | +@Slf4j | ||
| 22 | +public class IotRuleSceneDeviceControlAction implements IotRuleSceneAction { | ||
| 23 | + | ||
| 24 | + @Resource | ||
| 25 | + private IotDeviceDownstreamService deviceDownstreamService; | ||
| 26 | + @Resource | ||
| 27 | + private IotDeviceService deviceService; | ||
| 28 | + | ||
| 29 | + @Override | ||
| 30 | + public void execute(IotDeviceMessage message, IotRuleSceneDO.ActionConfig config) { | ||
| 31 | + IotRuleSceneDO.ActionDeviceControl control = config.getDeviceControl(); | ||
| 32 | + Assert.notNull(control, "设备控制配置不能为空"); | ||
| 33 | + // 遍历每个设备,下发消息 | ||
| 34 | + control.getDeviceNames().forEach(deviceName -> { | ||
| 35 | + IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(control.getProductKey(), deviceName); | ||
| 36 | + if (device == null) { | ||
| 37 | + log.error("[execute][message({}) config({}) 对应的设备不存在]", message, config); | ||
| 38 | + return; | ||
| 39 | + } | ||
| 40 | + try { | ||
| 41 | + IotDeviceMessage downstreamMessage = deviceDownstreamService.downstreamDevice(new IotDeviceDownstreamReqVO() | ||
| 42 | + .setId(device.getId()).setType(control.getType()).setIdentifier(control.getIdentifier()) | ||
| 43 | + .setData(control.getData())); | ||
| 44 | + log.info("[execute][message({}) config({}) 下发消息({})成功]", message, config, downstreamMessage); | ||
| 45 | + } catch (Exception e) { | ||
| 46 | + log.error("[execute][message({}) config({}) 下发消息失败]", message, config, e); | ||
| 47 | + } | ||
| 48 | + }); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + @Override | ||
| 52 | + public IotRuleSceneActionTypeEnum getType() { | ||
| 53 | + return IotRuleSceneActionTypeEnum.DEVICE_CONTROL; | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | +} |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java
| @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; | @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; | ||
| 6 | import cn.hutool.extra.spring.SpringUtil; | 6 | import cn.hutool.extra.spring.SpringUtil; |
| 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 9 | -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | 9 | +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; |
| 10 | import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent; | 10 | import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent; |
| 11 | import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelParam; | 11 | import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelParam; |
| 12 | import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService; | 12 | import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService; |
| @@ -135,13 +135,9 @@ public class IotThingModelServiceImpl implements IotThingModelService { | @@ -135,13 +135,9 @@ public class IotThingModelServiceImpl implements IotThingModelService { | ||
| 135 | } | 135 | } |
| 136 | 136 | ||
| 137 | @Override | 137 | @Override |
| 138 | - public List<IotThingModelDO> getThingModelListByProductKeyFromCache(String productKey) { | ||
| 139 | - // 保证在 @CacheEvict 之前,忽略租户 | ||
| 140 | - return TenantUtils.executeIgnore(() -> getSelf().getThingModelListByProductKeyFromCache0(productKey)); | ||
| 141 | - } | ||
| 142 | - | ||
| 143 | @Cacheable(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productKey") | 138 | @Cacheable(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productKey") |
| 144 | - public List<IotThingModelDO> getThingModelListByProductKeyFromCache0(String productKey) { | 139 | + @TenantIgnore // 忽略租户信息,跨租户 productKey 是唯一的 |
| 140 | + public List<IotThingModelDO> getThingModelListByProductKeyFromCache(String productKey) { | ||
| 145 | return thingModelMapper.selectListByProductKey(productKey); | 141 | return thingModelMapper.selectListByProductKey(productKey); |
| 146 | } | 142 | } |
| 147 | 143 | ||
| @@ -354,8 +350,8 @@ public class IotThingModelServiceImpl implements IotThingModelService { | @@ -354,8 +350,8 @@ public class IotThingModelServiceImpl implements IotThingModelService { | ||
| 354 | } | 350 | } |
| 355 | 351 | ||
| 356 | private void deleteThingModelListCache(String productKey) { | 352 | private void deleteThingModelListCache(String productKey) { |
| 357 | - // 保证在 @CacheEvict 之前,忽略租户 | ||
| 358 | - TenantUtils.executeIgnore(() -> getSelf().deleteThingModelListCache0(productKey)); | 353 | + // 保证 Spring AOP 触发 |
| 354 | + getSelf().deleteThingModelListCache0(productKey); | ||
| 359 | } | 355 | } |
| 360 | 356 | ||
| 361 | @CacheEvict(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productKey") | 357 | @CacheEvict(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productKey") |
yudao-server/src/main/resources/application.yaml
| @@ -311,6 +311,8 @@ yudao: | @@ -311,6 +311,8 @@ yudao: | ||
| 311 | - mail_account | 311 | - mail_account |
| 312 | - mail_template | 312 | - mail_template |
| 313 | - sms_template | 313 | - sms_template |
| 314 | + - iot:device | ||
| 315 | + - iot:thing_model_list | ||
| 314 | sms-code: # 短信验证码相关的配置项 | 316 | sms-code: # 短信验证码相关的配置项 |
| 315 | expire-times: 10m | 317 | expire-times: 10m |
| 316 | send-frequency: 1m | 318 | send-frequency: 1m |