Commit 39aaeaa2983a1e7bfaf5a5198e5b2f32b4f4793e
1 parent
eb74f753
【功能新增】IoT:增加 IotDeviceOfflineCheckJob,处理设备超时下线
Showing
10 changed files
with
135 additions
and
19 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
| ... | ... | @@ -122,7 +122,7 @@ public class IotDeviceController { |
| 122 | 122 | @Parameter(name = "deviceType", description = "设备类型", example = "1") |
| 123 | 123 | public CommonResult<List<IotDeviceRespVO>> getSimpleDeviceList( |
| 124 | 124 | @RequestParam(value = "deviceType", required = false) Integer deviceType) { |
| 125 | - List<IotDeviceDO> list = deviceService.getDeviceList(deviceType); | |
| 125 | + List<IotDeviceDO> list = deviceService.getDeviceListByDeviceType(deviceType); | |
| 126 | 126 | return success(convertList(list, device -> // 只返回 id、name 字段 |
| 127 | 127 | new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName()))); |
| 128 | 128 | } | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java
| ... | ... | @@ -52,10 +52,14 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> { |
| 52 | 52 | .apply("LOWER(device_key) = {0}", deviceKey.toLowerCase())); |
| 53 | 53 | } |
| 54 | 54 | |
| 55 | - default List<IotDeviceDO> selectList(Integer deviceType) { | |
| 55 | + default List<IotDeviceDO> selectListByDeviceType(Integer deviceType) { | |
| 56 | 56 | return selectList(IotDeviceDO::getDeviceType, deviceType); |
| 57 | 57 | } |
| 58 | 58 | |
| 59 | + default List<IotDeviceDO> selectListByState(Integer state) { | |
| 60 | + return selectList(IotDeviceDO::getState, state); | |
| 61 | + } | |
| 62 | + | |
| 59 | 63 | default Long selectCountByGroupId(Long groupId) { |
| 60 | 64 | return selectCount(new LambdaQueryWrapperX<IotDeviceDO>() |
| 61 | 65 | .apply("FIND_IN_SET(" + groupId + ",group_ids) > 0") | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java
| ... | ... | @@ -24,7 +24,7 @@ public interface RedisKeyConstants { |
| 24 | 24 | * KEY 格式:{deviceKey} |
| 25 | 25 | * SCORE:上报时间 |
| 26 | 26 | */ |
| 27 | - String DEVICE_REPORT_TIME = "device_report_time"; | |
| 27 | + String DEVICE_REPORT_TIMES = "device_report_times"; | |
| 28 | 28 | |
| 29 | 29 | /** |
| 30 | 30 | * 设备信息的数据缓存,使用 Spring Cache 操作 |
| ... | ... | @@ -32,7 +32,7 @@ public interface RedisKeyConstants { |
| 32 | 32 | * KEY 格式:device_${productKey}_${deviceKey} |
| 33 | 33 | * VALUE 数据类型:String(JSON) |
| 34 | 34 | */ |
| 35 | - String DEVICE = "device"; | |
| 35 | + String DEVICE = "device"; | |
| 36 | 36 | |
| 37 | 37 | /** |
| 38 | 38 | * 物模型的数据缓存,使用 Spring Cache 操作 | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DeviceReportTimeRedisDAO.java
| ... | ... | @@ -7,6 +7,7 @@ import org.springframework.data.redis.core.StringRedisTemplate; |
| 7 | 7 | import org.springframework.stereotype.Repository; |
| 8 | 8 | |
| 9 | 9 | import java.time.LocalDateTime; |
| 10 | +import java.util.Set; | |
| 10 | 11 | |
| 11 | 12 | /** |
| 12 | 13 | * 设备的最后上报时间的 Redis DAO |
| ... | ... | @@ -20,8 +21,13 @@ public class DeviceReportTimeRedisDAO { |
| 20 | 21 | private StringRedisTemplate stringRedisTemplate; |
| 21 | 22 | |
| 22 | 23 | public void update(String deviceKey, LocalDateTime reportTime) { |
| 23 | - stringRedisTemplate.opsForZSet().add(RedisKeyConstants.DEVICE_REPORT_TIME, deviceKey, | |
| 24 | + stringRedisTemplate.opsForZSet().add(RedisKeyConstants.DEVICE_REPORT_TIMES, deviceKey, | |
| 24 | 25 | LocalDateTimeUtil.toEpochMilli(reportTime)); |
| 25 | 26 | } |
| 26 | 27 | |
| 28 | + public Set<String> range(LocalDateTime maxReportTime) { | |
| 29 | + return stringRedisTemplate.opsForZSet().rangeByScore(RedisKeyConstants.DEVICE_REPORT_TIMES, 0, | |
| 30 | + LocalDateTimeUtil.toEpochMilli(maxReportTime)); | |
| 31 | + } | |
| 32 | + | |
| 27 | 33 | } | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/device/IotDeviceOfflineCheckJob.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.job.device; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.core.util.IdUtil; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 3 | 6 | import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; |
| 4 | 7 | import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; |
| 8 | +import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStateUpdateReqDTO; | |
| 9 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | |
| 10 | +import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum; | |
| 11 | +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; | |
| 12 | +import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService; | |
| 13 | +import cn.iocoder.yudao.module.iot.service.device.upstream.IotDeviceUpstreamService; | |
| 14 | +import jakarta.annotation.Resource; | |
| 5 | 15 | import org.springframework.stereotype.Component; |
| 6 | 16 | |
| 7 | -// TODO @芋艿:待实现 | |
| 17 | +import java.time.Duration; | |
| 18 | +import java.time.LocalDateTime; | |
| 19 | +import java.util.Collections; | |
| 20 | +import java.util.List; | |
| 21 | +import java.util.Set; | |
| 22 | + | |
| 8 | 23 | /** |
| 9 | 24 | * IoT 设备离线检查 Job |
| 10 | 25 | * |
| ... | ... | @@ -15,10 +30,46 @@ import org.springframework.stereotype.Component; |
| 15 | 30 | @Component |
| 16 | 31 | public class IotDeviceOfflineCheckJob implements JobHandler { |
| 17 | 32 | |
| 33 | + /** | |
| 34 | + * 设备离线超时时间 | |
| 35 | + * | |
| 36 | + * TODO 芋艿:暂定 10 分钟,后续看看要不要基于设备或者全局有配置文件 | |
| 37 | + */ | |
| 38 | + public static final Duration OFFLINE_TIMEOUT = Duration.ofMinutes(10); | |
| 39 | + | |
| 40 | + @Resource | |
| 41 | + private IotDeviceService deviceService; | |
| 42 | + @Resource | |
| 43 | + private IotDevicePropertyService devicePropertyService; | |
| 44 | + @Resource | |
| 45 | + private IotDeviceUpstreamService deviceUpstreamService; | |
| 46 | + | |
| 18 | 47 | @Override |
| 19 | 48 | @TenantJob |
| 20 | 49 | public String execute(String param) { |
| 21 | - return ""; | |
| 50 | + // 1.1 获得在线设备列表 | |
| 51 | + List<IotDeviceDO> devices = deviceService.getDeviceListByState(IotDeviceStateEnum.ONLINE.getState()); | |
| 52 | + if (CollUtil.isEmpty(devices)) { | |
| 53 | + return JsonUtils.toJsonString(Collections.emptyList()); | |
| 54 | + } | |
| 55 | + // 1.2 获取超时的 deviceKey 集合 | |
| 56 | + Set<String> timeoutDeviceKeys = devicePropertyService.getDeviceKeysByReportTime( | |
| 57 | + LocalDateTime.now().minus(OFFLINE_TIMEOUT)); | |
| 58 | + | |
| 59 | + // 2. 下线设备 | |
| 60 | + List<String> offlineDeviceKeys = CollUtil.newArrayList(); | |
| 61 | + for (IotDeviceDO device : devices) { | |
| 62 | + if (!timeoutDeviceKeys.contains(device.getDeviceKey())) { | |
| 63 | + continue; | |
| 64 | + } | |
| 65 | + offlineDeviceKeys.add(device.getDeviceKey()); | |
| 66 | + // 为什么不直接更新状态呢?因为通过 IotDeviceMessage 可以经过一系列的处理,例如说记录日志等等 | |
| 67 | + deviceUpstreamService.updateDeviceState(((IotDeviceStateUpdateReqDTO) | |
| 68 | + new IotDeviceStateUpdateReqDTO().setRequestId(IdUtil.fastSimpleUUID()).setReportTime(LocalDateTime.now()) | |
| 69 | + .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName())) | |
| 70 | + .setState((IotDeviceStateEnum.OFFLINE.getState()))); | |
| 71 | + } | |
| 72 | + return JsonUtils.toJsonString(offlineDeviceKeys); | |
| 22 | 73 | } |
| 23 | 74 | |
| 24 | 75 | } | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
| ... | ... | @@ -93,15 +93,23 @@ public interface IotDeviceService { |
| 93 | 93 | PageResult<IotDeviceDO> getDevicePage(IotDevicePageReqVO pageReqVO); |
| 94 | 94 | |
| 95 | 95 | /** |
| 96 | - * 获得设备列表 | |
| 96 | + * 基于设备类型,获得设备列表 | |
| 97 | 97 | * |
| 98 | 98 | * @param deviceType 设备类型 |
| 99 | 99 | * @return 设备列表 |
| 100 | 100 | */ |
| 101 | - List<IotDeviceDO> getDeviceList(@Nullable Integer deviceType); | |
| 101 | + List<IotDeviceDO> getDeviceListByDeviceType(@Nullable Integer deviceType); | |
| 102 | 102 | |
| 103 | 103 | /** |
| 104 | - * 获得设备数量 | |
| 104 | + * 获得状态,获得设备列表 | |
| 105 | + * | |
| 106 | + * @param state 状态 | |
| 107 | + * @return 设备列表 | |
| 108 | + */ | |
| 109 | + List<IotDeviceDO> getDeviceListByState(Integer state); | |
| 110 | + | |
| 111 | + /** | |
| 112 | + * 基于产品编号,获得设备数量 | |
| 105 | 113 | * |
| 106 | 114 | * @param productId 产品编号 |
| 107 | 115 | * @return 设备数量 | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
| ... | ... | @@ -226,8 +226,13 @@ public class IotDeviceServiceImpl implements IotDeviceService { |
| 226 | 226 | } |
| 227 | 227 | |
| 228 | 228 | @Override |
| 229 | - public List<IotDeviceDO> getDeviceList(@Nullable Integer deviceType) { | |
| 230 | - return deviceMapper.selectList(deviceType); | |
| 229 | + public List<IotDeviceDO> getDeviceListByDeviceType(@Nullable Integer deviceType) { | |
| 230 | + return deviceMapper.selectListByDeviceType(deviceType); | |
| 231 | + } | |
| 232 | + | |
| 233 | + @Override | |
| 234 | + public List<IotDeviceDO> getDeviceListByState(Integer state) { | |
| 235 | + return deviceMapper.selectListByState(state); | |
| 231 | 236 | } |
| 232 | 237 | |
| 233 | 238 | @Override | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyService.java
| ... | ... | @@ -7,7 +7,9 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO; |
| 7 | 7 | import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; |
| 8 | 8 | import jakarta.validation.Valid; |
| 9 | 9 | |
| 10 | +import java.time.LocalDateTime; | |
| 10 | 11 | import java.util.Map; |
| 12 | +import java.util.Set; | |
| 11 | 13 | |
| 12 | 14 | /** |
| 13 | 15 | * IoT 设备【属性】数据 Service 接口 |
| ... | ... | @@ -16,6 +18,8 @@ import java.util.Map; |
| 16 | 18 | */ |
| 17 | 19 | public interface IotDevicePropertyService { |
| 18 | 20 | |
| 21 | + // ========== 设备属性相关操作 ========== | |
| 22 | + | |
| 19 | 23 | /** |
| 20 | 24 | * 定义设备属性数据的结构 |
| 21 | 25 | * |
| ... | ... | @@ -46,4 +50,22 @@ public interface IotDevicePropertyService { |
| 46 | 50 | */ |
| 47 | 51 | PageResult<IotDevicePropertyRespVO> getHistoryDevicePropertyPage(@Valid IotDevicePropertyHistoryPageReqVO pageReqVO); |
| 48 | 52 | |
| 53 | + // ========== 设备时间相关操作 ========== | |
| 54 | + | |
| 55 | + /** | |
| 56 | + * 获得最后上报时间小于指定时间的设备标识 | |
| 57 | + * | |
| 58 | + * @param maxReportTime 最大上报时间 | |
| 59 | + * @return 设备标识列表 | |
| 60 | + */ | |
| 61 | + Set<String> getDeviceKeysByReportTime(LocalDateTime maxReportTime); | |
| 62 | + | |
| 63 | + /** | |
| 64 | + * 更新设备上报时间 | |
| 65 | + * | |
| 66 | + * @param deviceKey 设备标识 | |
| 67 | + * @param reportTime 上报时间 | |
| 68 | + */ | |
| 69 | + void updateDeviceReportTime(String deviceKey, LocalDateTime reportTime); | |
| 70 | + | |
| 49 | 71 | } |
| 50 | 72 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyServiceImpl.java
| ... | ... | @@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO; |
| 14 | 14 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; |
| 15 | 15 | import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; |
| 16 | 16 | import cn.iocoder.yudao.module.iot.dal.redis.device.DevicePropertyRedisDAO; |
| 17 | +import cn.iocoder.yudao.module.iot.dal.redis.device.DeviceReportTimeRedisDAO; | |
| 17 | 18 | import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyMapper; |
| 18 | 19 | import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; |
| 19 | 20 | import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; |
| ... | ... | @@ -28,10 +29,8 @@ import jakarta.annotation.Resource; |
| 28 | 29 | import lombok.extern.slf4j.Slf4j; |
| 29 | 30 | import org.springframework.stereotype.Service; |
| 30 | 31 | |
| 31 | -import java.util.ArrayList; | |
| 32 | -import java.util.HashMap; | |
| 33 | -import java.util.List; | |
| 34 | -import java.util.Map; | |
| 32 | +import java.time.LocalDateTime; | |
| 33 | +import java.util.*; | |
| 35 | 34 | |
| 36 | 35 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| 37 | 36 | |
| ... | ... | @@ -68,10 +67,14 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { |
| 68 | 67 | |
| 69 | 68 | @Resource |
| 70 | 69 | private DevicePropertyRedisDAO deviceDataRedisDAO; |
| 70 | + @Resource | |
| 71 | + private DeviceReportTimeRedisDAO deviceReportTimeRedisDAO; | |
| 71 | 72 | |
| 72 | 73 | @Resource |
| 73 | 74 | private IotDevicePropertyMapper devicePropertyMapper; |
| 74 | 75 | |
| 76 | + // ========== 设备属性相关操作 ========== | |
| 77 | + | |
| 75 | 78 | @Override |
| 76 | 79 | public void defineDevicePropertyData(Long productId) { |
| 77 | 80 | // 1.1 查询产品和物模型 |
| ... | ... | @@ -179,4 +182,16 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { |
| 179 | 182 | } |
| 180 | 183 | } |
| 181 | 184 | |
| 185 | + // ========== 设备时间相关操作 ========== | |
| 186 | + | |
| 187 | + @Override | |
| 188 | + public Set<String> getDeviceKeysByReportTime(LocalDateTime maxReportTime) { | |
| 189 | + return deviceReportTimeRedisDAO.range(maxReportTime); | |
| 190 | + } | |
| 191 | + | |
| 192 | + @Override | |
| 193 | + public void updateDeviceReportTime(String deviceKey, LocalDateTime reportTime) { | |
| 194 | + deviceReportTimeRedisDAO.update(deviceKey, reportTime); | |
| 195 | + } | |
| 196 | + | |
| 182 | 197 | } |
| 183 | 198 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/upstream/IotDeviceUpstreamServiceImpl.java
| ... | ... | @@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum; |
| 18 | 18 | import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; |
| 19 | 19 | import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer; |
| 20 | 20 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; |
| 21 | +import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService; | |
| 21 | 22 | import jakarta.annotation.Resource; |
| 22 | 23 | import lombok.extern.slf4j.Slf4j; |
| 23 | 24 | import org.springframework.stereotype.Service; |
| ... | ... | @@ -38,6 +39,8 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { |
| 38 | 39 | |
| 39 | 40 | @Resource |
| 40 | 41 | private IotDeviceService deviceService; |
| 42 | + @Resource | |
| 43 | + private IotDevicePropertyService devicePropertyService; | |
| 41 | 44 | |
| 42 | 45 | @Resource |
| 43 | 46 | private IotDeviceProducer deviceProducer; |
| ... | ... | @@ -107,9 +110,11 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { |
| 107 | 110 | // TODO 芋艿:待实现 |
| 108 | 111 | } |
| 109 | 112 | |
| 110 | - private void updateDeviceLastTime(IotDeviceDO deviceDO, IotDeviceUpstreamAbstractReqDTO reqDTO) { | |
| 111 | - // TODO 芋艿:插件状态 | |
| 112 | - // TODO 芋艿:操作时间 | |
| 113 | + private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) { | |
| 114 | + // 1. TODO 芋艿:插件状态 | |
| 115 | + | |
| 116 | + // 2. 更新设备的最后时间 | |
| 117 | + devicePropertyService.updateDeviceReportTime(device.getDeviceKey(), LocalDateTime.now()); | |
| 113 | 118 | } |
| 114 | 119 | |
| 115 | 120 | private void sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device) { | ... | ... |