Commit 1498389d260435221c6c143d7f0628c44473efca
1 parent
0bb01eae
feat:【IoT 物联网】增加(重构) IotDevicePropertyMessageSubscriber 记录设备属性
Showing
8 changed files
with
59 additions
and
39 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDevicePropertyHistoryPageReqVO.java
| @@ -20,8 +20,11 @@ public class IotDevicePropertyHistoryPageReqVO extends PageParam { | @@ -20,8 +20,11 @@ public class IotDevicePropertyHistoryPageReqVO extends PageParam { | ||
| 20 | @NotNull(message = "设备编号不能为空") | 20 | @NotNull(message = "设备编号不能为空") |
| 21 | private Long deviceId; | 21 | private Long deviceId; |
| 22 | 22 | ||
| 23 | - @Schema(description = "设备 Key", hidden = true) | ||
| 24 | - private String deviceKey; // 非前端传递,后端自己查询设置 | 23 | + @Schema(description = "产品 Key", hidden = true) |
| 24 | + private String productKey; // 非前端传递,后端自己查询设置 | ||
| 25 | + | ||
| 26 | + @Schema(description = "设备名称", hidden = true) | ||
| 27 | + private String deviceName; // 非前端传递,后端自己查询设置 | ||
| 25 | 28 | ||
| 26 | @Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED) | 29 | @Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED) |
| 27 | @NotEmpty(message = "属性标识符不能为空") | 30 | @NotEmpty(message = "属性标识符不能为空") |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java
| @@ -9,15 +9,14 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO; | @@ -9,15 +9,14 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO; | ||
| 9 | */ | 9 | */ |
| 10 | public interface RedisKeyConstants { | 10 | public interface RedisKeyConstants { |
| 11 | 11 | ||
| 12 | - // TODO @芋艿:弱化 deviceKey;使用 product_key + device_name 替代 | ||
| 13 | /** | 12 | /** |
| 14 | * 设备属性的数据缓存,采用 HASH 结构 | 13 | * 设备属性的数据缓存,采用 HASH 结构 |
| 15 | * <p> | 14 | * <p> |
| 16 | - * KEY 格式:device_property:{deviceKey} | 15 | + * KEY 格式:device_property:{productKey},${deviceName} |
| 17 | * HASH KEY:identifier 属性标识 | 16 | * HASH KEY:identifier 属性标识 |
| 18 | * VALUE 数据类型:String(JSON) {@link IotDevicePropertyDO} | 17 | * VALUE 数据类型:String(JSON) {@link IotDevicePropertyDO} |
| 19 | */ | 18 | */ |
| 20 | - String DEVICE_PROPERTY = "iot:device_property:%s"; | 19 | + String DEVICE_PROPERTY = "iot:device_property:%s,%s"; |
| 21 | 20 | ||
| 22 | /** | 21 | /** |
| 23 | * 设备的最后上报时间,采用 ZSET 结构 | 22 | * 设备的最后上报时间,采用 ZSET 结构 |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DevicePropertyRedisDAO.java
| @@ -22,8 +22,8 @@ public class DevicePropertyRedisDAO { | @@ -22,8 +22,8 @@ public class DevicePropertyRedisDAO { | ||
| 22 | @Resource | 22 | @Resource |
| 23 | private StringRedisTemplate stringRedisTemplate; | 23 | private StringRedisTemplate stringRedisTemplate; |
| 24 | 24 | ||
| 25 | - public Map<String, IotDevicePropertyDO> get(String deviceKey) { | ||
| 26 | - String redisKey = formatKey(deviceKey); | 25 | + public Map<String, IotDevicePropertyDO> get(String productKey, String deviceName) { |
| 26 | + String redisKey = formatKey(productKey, deviceName); | ||
| 27 | Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(redisKey); | 27 | Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(redisKey); |
| 28 | if (CollUtil.isEmpty(entries)) { | 28 | if (CollUtil.isEmpty(entries)) { |
| 29 | return Collections.emptyMap(); | 29 | return Collections.emptyMap(); |
| @@ -33,18 +33,18 @@ public class DevicePropertyRedisDAO { | @@ -33,18 +33,18 @@ public class DevicePropertyRedisDAO { | ||
| 33 | entry -> JsonUtils.parseObject((String) entry.getValue(), IotDevicePropertyDO.class)); | 33 | entry -> JsonUtils.parseObject((String) entry.getValue(), IotDevicePropertyDO.class)); |
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | - public void putAll(String deviceKey, Map<String, IotDevicePropertyDO> properties) { | 36 | + public void putAll(String productKey, String deviceName, Map<String, IotDevicePropertyDO> properties) { |
| 37 | if (CollUtil.isEmpty(properties)) { | 37 | if (CollUtil.isEmpty(properties)) { |
| 38 | return; | 38 | return; |
| 39 | } | 39 | } |
| 40 | - String redisKey = formatKey(deviceKey); | 40 | + String redisKey = formatKey(productKey, deviceName); |
| 41 | stringRedisTemplate.opsForHash().putAll(redisKey, convertMap(properties.entrySet(), | 41 | stringRedisTemplate.opsForHash().putAll(redisKey, convertMap(properties.entrySet(), |
| 42 | Map.Entry::getKey, | 42 | Map.Entry::getKey, |
| 43 | entry -> JsonUtils.toJsonString(entry.getValue()))); | 43 | entry -> JsonUtils.toJsonString(entry.getValue()))); |
| 44 | } | 44 | } |
| 45 | 45 | ||
| 46 | - private static String formatKey(String deviceKey) { | ||
| 47 | - return String.format(DEVICE_PROPERTY, deviceKey); | 46 | + private static String formatKey(String productKey, String deviceName) { |
| 47 | + return String.format(DEVICE_PROPERTY, productKey, deviceName); | ||
| 48 | } | 48 | } |
| 49 | 49 | ||
| 50 | } | 50 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDevicePropertyMapper.java
| @@ -33,7 +33,7 @@ public interface IotDevicePropertyMapper { | @@ -33,7 +33,7 @@ public interface IotDevicePropertyMapper { | ||
| 33 | List<TDengineTableField> oldFields, | 33 | List<TDengineTableField> oldFields, |
| 34 | List<TDengineTableField> newFields) { | 34 | List<TDengineTableField> newFields) { |
| 35 | oldFields.removeIf(field -> StrUtil.equalsAny(field.getField(), | 35 | oldFields.removeIf(field -> StrUtil.equalsAny(field.getField(), |
| 36 | - TDengineTableField.FIELD_TS, "report_time", "device_key")); | 36 | + TDengineTableField.FIELD_TS, "report_time", "device_name")); |
| 37 | List<TDengineTableField> addFields = newFields.stream().filter( // 新增的字段 | 37 | List<TDengineTableField> addFields = newFields.stream().filter( // 新增的字段 |
| 38 | newField -> oldFields.stream().noneMatch(oldField -> oldField.getField().equals(newField.getField()))) | 38 | newField -> oldFields.stream().noneMatch(oldField -> oldField.getField().equals(newField.getField()))) |
| 39 | .collect(Collectors.toList()); | 39 | .collect(Collectors.toList()); |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/device/IotDevicePropertyMessageConsumer.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/device/IotDevicePropertyMessageSubscriber.java
| 1 | package cn.iocoder.yudao.module.iot.mq.consumer.device; | 1 | package cn.iocoder.yudao.module.iot.mq.consumer.device; |
| 2 | 2 | ||
| 3 | -import cn.hutool.core.util.ObjectUtil; | 3 | +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; |
| 4 | +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; | ||
| 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 5 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 5 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum; | 6 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum; |
| 6 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum; | 7 | import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum; |
| 7 | import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService; | 8 | import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService; |
| 9 | +import com.google.common.base.Objects; | ||
| 10 | +import jakarta.annotation.PostConstruct; | ||
| 11 | +import jakarta.annotation.Resource; | ||
| 8 | import lombok.extern.slf4j.Slf4j; | 12 | import lombok.extern.slf4j.Slf4j; |
| 9 | -import org.springframework.context.event.EventListener; | ||
| 10 | -import org.springframework.scheduling.annotation.Async; | ||
| 11 | import org.springframework.stereotype.Component; | 13 | import org.springframework.stereotype.Component; |
| 12 | 14 | ||
| 13 | -import jakarta.annotation.Resource; | ||
| 14 | - | ||
| 15 | /** | 15 | /** |
| 16 | - * 针对 {@link IotDeviceMessage} 的消费者,记录设备属性 | 16 | + * 针对 {@link IotDeviceMessage} 的消费者:记录设备属性 |
| 17 | * | 17 | * |
| 18 | * @author alwayssuper | 18 | * @author alwayssuper |
| 19 | */ | 19 | */ |
| 20 | @Component | 20 | @Component |
| 21 | @Slf4j | 21 | @Slf4j |
| 22 | -public class IotDevicePropertyMessageConsumer { | 22 | +public class IotDevicePropertyMessageSubscriber implements IotMessageSubscriber<IotDeviceMessage> { |
| 23 | 23 | ||
| 24 | @Resource | 24 | @Resource |
| 25 | private IotDevicePropertyService deviceDataService; | 25 | private IotDevicePropertyService deviceDataService; |
| 26 | 26 | ||
| 27 | - @EventListener | ||
| 28 | - @Async | 27 | + @Resource |
| 28 | + private IotMessageBus messageBus; | ||
| 29 | + | ||
| 30 | + @PostConstruct | ||
| 31 | + public void init() { | ||
| 32 | + messageBus.register(this); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + @Override | ||
| 36 | + public String getTopic() { | ||
| 37 | + return IotDeviceMessage.MESSAGE_BUS_DEVICE_MESSAGE_TOPIC; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + @Override | ||
| 41 | + public String getGroup() { | ||
| 42 | + return "iot_device_property_consumer"; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + @Override | ||
| 29 | public void onMessage(IotDeviceMessage message) { | 46 | public void onMessage(IotDeviceMessage message) { |
| 30 | - if (ObjectUtil.notEqual(message.getType(), IotDeviceMessageTypeEnum.PROPERTY.getType()) | ||
| 31 | - || ObjectUtil.notEqual(message.getIdentifier(), IotDeviceMessageIdentifierEnum.PROPERTY_REPORT.getIdentifier())) { | ||
| 32 | - return; | 47 | + if (Objects.equal(message.getType(), IotDeviceMessageTypeEnum.PROPERTY.getType()) |
| 48 | + && Objects.equal(message.getIdentifier(), IotDeviceMessageIdentifierEnum.PROPERTY_REPORT.getIdentifier())) { | ||
| 49 | + // 保存设备属性 | ||
| 50 | + deviceDataService.saveDeviceProperty(message); | ||
| 33 | } | 51 | } |
| 34 | - log.info("[onMessage][消息内容({})]", message); | ||
| 35 | - | ||
| 36 | - // 保存设备属性 | ||
| 37 | - deviceDataService.saveDeviceProperty(message); | ||
| 38 | } | 52 | } |
| 39 | 53 | ||
| 40 | } | 54 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotRuleSceneMessageHandler.java
| @@ -8,6 +8,7 @@ import org.springframework.context.event.EventListener; | @@ -8,6 +8,7 @@ import org.springframework.context.event.EventListener; | ||
| 8 | import org.springframework.scheduling.annotation.Async; | 8 | import org.springframework.scheduling.annotation.Async; |
| 9 | import org.springframework.stereotype.Component; | 9 | import org.springframework.stereotype.Component; |
| 10 | 10 | ||
| 11 | +// TODO @puhui999:后面重构哈 | ||
| 11 | /** | 12 | /** |
| 12 | * 针对 {@link IotDeviceMessage} 的消费者,处理规则场景 | 13 | * 针对 {@link IotDeviceMessage} 的消费者,处理规则场景 |
| 13 | * | 14 | * |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyServiceImpl.java
| @@ -128,7 +128,8 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { | @@ -128,7 +128,8 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { | ||
| 128 | return; | 128 | return; |
| 129 | } | 129 | } |
| 130 | // 1. 获得设备信息 | 130 | // 1. 获得设备信息 |
| 131 | - IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(message.getProductKey(), message.getDeviceName()); | 131 | + IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache( |
| 132 | + message.getProductKey(), message.getDeviceName()); | ||
| 132 | if (device == null) { | 133 | if (device == null) { |
| 133 | log.error("[saveDeviceProperty][消息({}) 对应的设备不存在]", message); | 134 | log.error("[saveDeviceProperty][消息({}) 对应的设备不存在]", message); |
| 134 | return; | 135 | return; |
| @@ -155,9 +156,9 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { | @@ -155,9 +156,9 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { | ||
| 155 | LocalDateTimeUtil.toEpochMilli(message.getReportTime())); | 156 | LocalDateTimeUtil.toEpochMilli(message.getReportTime())); |
| 156 | 157 | ||
| 157 | // 3.2 保存设备属性【日志】 | 158 | // 3.2 保存设备属性【日志】 |
| 158 | - // TODO @芋艿:这里要调整下; | ||
| 159 | - deviceDataRedisDAO.putAll(device.getDeviceKey(), convertMap(properties.entrySet(), Map.Entry::getKey, | ||
| 160 | - entry -> IotDevicePropertyDO.builder().value(entry.getValue()).updateTime(message.getReportTime()).build())); | 159 | + Map<String, IotDevicePropertyDO> properties2 = convertMap(properties.entrySet(), Map.Entry::getKey, entry -> |
| 160 | + IotDevicePropertyDO.builder().value(entry.getValue()).updateTime(message.getReportTime()).build()); | ||
| 161 | + deviceDataRedisDAO.putAll(device.getProductKey(), device.getDeviceName(), properties2); | ||
| 161 | } | 162 | } |
| 162 | 163 | ||
| 163 | @Override | 164 | @Override |
| @@ -166,15 +167,16 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { | @@ -166,15 +167,16 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { | ||
| 166 | IotDeviceDO device = deviceService.validateDeviceExists(deviceId); | 167 | IotDeviceDO device = deviceService.validateDeviceExists(deviceId); |
| 167 | 168 | ||
| 168 | // 获得设备属性 | 169 | // 获得设备属性 |
| 169 | - return deviceDataRedisDAO.get(device.getDeviceKey()); | 170 | + return deviceDataRedisDAO.get(device.getProductKey(), device.getDeviceName()); |
| 170 | } | 171 | } |
| 171 | 172 | ||
| 172 | @Override | 173 | @Override |
| 173 | public PageResult<IotDevicePropertyRespVO> getHistoryDevicePropertyPage(IotDevicePropertyHistoryPageReqVO pageReqVO) { | 174 | public PageResult<IotDevicePropertyRespVO> getHistoryDevicePropertyPage(IotDevicePropertyHistoryPageReqVO pageReqVO) { |
| 174 | // 获取设备信息 | 175 | // 获取设备信息 |
| 175 | IotDeviceDO device = deviceService.validateDeviceExists(pageReqVO.getDeviceId()); | 176 | IotDeviceDO device = deviceService.validateDeviceExists(pageReqVO.getDeviceId()); |
| 176 | - pageReqVO.setDeviceKey(device.getDeviceKey()); | 177 | + pageReqVO.setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()); |
| 177 | 178 | ||
| 179 | + // 分页查询 | ||
| 178 | try { | 180 | try { |
| 179 | IPage<IotDevicePropertyRespVO> page = devicePropertyMapper.selectPageByHistory( | 181 | IPage<IotDevicePropertyRespVO> page = devicePropertyMapper.selectPageByHistory( |
| 180 | new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), pageReqVO); | 182 | new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), pageReqVO); |
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDevicePropertyMapper.xml
| @@ -20,7 +20,7 @@ | @@ -20,7 +20,7 @@ | ||
| 20 | </foreach> | 20 | </foreach> |
| 21 | ) | 21 | ) |
| 22 | TAGS ( | 22 | TAGS ( |
| 23 | - device_key NCHAR(50) | 23 | + device_name NCHAR(50) |
| 24 | ) | 24 | ) |
| 25 | </update> | 25 | </update> |
| 26 | 26 | ||
| @@ -46,9 +46,9 @@ | @@ -46,9 +46,9 @@ | ||
| 46 | </update> | 46 | </update> |
| 47 | 47 | ||
| 48 | <insert id="insert"> | 48 | <insert id="insert"> |
| 49 | - INSERT INTO device_property_${device.deviceKey} | 49 | + INSERT INTO device_property_${device.productKey}_${device.deviceName} |
| 50 | USING product_property_${device.productKey} | 50 | USING product_property_${device.productKey} |
| 51 | - TAGS ('${device.deviceKey}') | 51 | + TAGS ('${device.deviceName}') |
| 52 | (ts, report_time, | 52 | (ts, report_time, |
| 53 | <foreach item="key" collection="properties.keys" separator=","> | 53 | <foreach item="key" collection="properties.keys" separator=","> |
| 54 | ${@cn.hutool.core.util.StrUtil@toUnderlineCase(key)} | 54 | ${@cn.hutool.core.util.StrUtil@toUnderlineCase(key)} |
| @@ -66,9 +66,10 @@ | @@ -66,9 +66,10 @@ | ||
| 66 | DESCRIBE product_property_${productKey} | 66 | DESCRIBE product_property_${productKey} |
| 67 | </select> | 67 | </select> |
| 68 | 68 | ||
| 69 | - <select id="selectPageByHistory" resultType="cn.iocoder.yudao.module.iot.controller.admin.device.vo.data.IotDevicePropertyRespVO"> | 69 | + <select id="selectPageByHistory" |
| 70 | + resultType="cn.iocoder.yudao.module.iot.controller.admin.device.vo.data.IotDevicePropertyRespVO"> | ||
| 70 | SELECT ${@cn.hutool.core.util.StrUtil@toUnderlineCase(reqVO.identifier)} AS `value`, ts AS update_time | 71 | SELECT ${@cn.hutool.core.util.StrUtil@toUnderlineCase(reqVO.identifier)} AS `value`, ts AS update_time |
| 71 | - FROM device_property_${reqVO.deviceKey} | 72 | + FROM device_property_${reqVO.productKey}_${reqVO.deviceName} |
| 72 | WHERE ${@cn.hutool.core.util.StrUtil@toUnderlineCase(reqVO.identifier)} IS NOT NULL | 73 | WHERE ${@cn.hutool.core.util.StrUtil@toUnderlineCase(reqVO.identifier)} IS NOT NULL |
| 73 | AND ts BETWEEN ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[0])} | 74 | AND ts BETWEEN ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[0])} |
| 74 | AND ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[1])} | 75 | AND ${@cn.hutool.core.date.LocalDateTimeUtil@toEpochMilli(reqVO.times[1])} |