diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java index a7180803d2..1708aba542 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java +++ b/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 { @Parameter(name = "deviceType", description = "设备类型", example = "1") public CommonResult> getSimpleDeviceList( @RequestParam(value = "deviceType", required = false) Integer deviceType) { - List list = deviceService.getDeviceList(deviceType); + List list = deviceService.getDeviceListByDeviceType(deviceType); return success(convertList(list, device -> // 只返回 id、name 字段 new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName()))); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java index ea80667e05..a3ae4e3807 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java +++ b/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 { .apply("LOWER(device_key) = {0}", deviceKey.toLowerCase())); } - default List selectList(Integer deviceType) { + default List selectListByDeviceType(Integer deviceType) { return selectList(IotDeviceDO::getDeviceType, deviceType); } + default List selectListByState(Integer state) { + return selectList(IotDeviceDO::getState, state); + } + default Long selectCountByGroupId(Long groupId) { return selectCount(new LambdaQueryWrapperX() .apply("FIND_IN_SET(" + groupId + ",group_ids) > 0") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java index d7096b55dd..8cff06c43c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java +++ b/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 { * KEY 格式:{deviceKey} * SCORE:上报时间 */ - String DEVICE_REPORT_TIME = "device_report_time"; + String DEVICE_REPORT_TIMES = "device_report_times"; /** * 设备信息的数据缓存,使用 Spring Cache 操作 @@ -32,7 +32,7 @@ public interface RedisKeyConstants { * KEY 格式:device_${productKey}_${deviceKey} * VALUE 数据类型:String(JSON) */ - String DEVICE = "device"; + String DEVICE = "device"; /** * 物模型的数据缓存,使用 Spring Cache 操作 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DeviceReportTimeRedisDAO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DeviceReportTimeRedisDAO.java index 35c99a06e0..d84af7543e 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DeviceReportTimeRedisDAO.java +++ b/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; import org.springframework.stereotype.Repository; import java.time.LocalDateTime; +import java.util.Set; /** * 设备的最后上报时间的 Redis DAO @@ -20,8 +21,13 @@ public class DeviceReportTimeRedisDAO { private StringRedisTemplate stringRedisTemplate; public void update(String deviceKey, LocalDateTime reportTime) { - stringRedisTemplate.opsForZSet().add(RedisKeyConstants.DEVICE_REPORT_TIME, deviceKey, + stringRedisTemplate.opsForZSet().add(RedisKeyConstants.DEVICE_REPORT_TIMES, deviceKey, LocalDateTimeUtil.toEpochMilli(reportTime)); } + public Set range(LocalDateTime maxReportTime) { + return stringRedisTemplate.opsForZSet().rangeByScore(RedisKeyConstants.DEVICE_REPORT_TIMES, 0, + LocalDateTimeUtil.toEpochMilli(maxReportTime)); + } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/device/IotDeviceOfflineCheckJob.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/device/IotDeviceOfflineCheckJob.java index 96f33e48c7..ddbd85b92d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/device/IotDeviceOfflineCheckJob.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/device/IotDeviceOfflineCheckJob.java @@ -1,10 +1,25 @@ package cn.iocoder.yudao.module.iot.job.device; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.IdUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStateUpdateReqDTO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; +import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum; +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; +import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService; +import cn.iocoder.yudao.module.iot.service.device.upstream.IotDeviceUpstreamService; +import jakarta.annotation.Resource; import org.springframework.stereotype.Component; -// TODO @芋艿:待实现 +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Set; + /** * IoT 设备离线检查 Job * @@ -15,10 +30,46 @@ import org.springframework.stereotype.Component; @Component public class IotDeviceOfflineCheckJob implements JobHandler { + /** + * 设备离线超时时间 + * + * TODO 芋艿:暂定 10 分钟,后续看看要不要基于设备或者全局有配置文件 + */ + public static final Duration OFFLINE_TIMEOUT = Duration.ofMinutes(10); + + @Resource + private IotDeviceService deviceService; + @Resource + private IotDevicePropertyService devicePropertyService; + @Resource + private IotDeviceUpstreamService deviceUpstreamService; + @Override @TenantJob public String execute(String param) { - return ""; + // 1.1 获得在线设备列表 + List devices = deviceService.getDeviceListByState(IotDeviceStateEnum.ONLINE.getState()); + if (CollUtil.isEmpty(devices)) { + return JsonUtils.toJsonString(Collections.emptyList()); + } + // 1.2 获取超时的 deviceKey 集合 + Set timeoutDeviceKeys = devicePropertyService.getDeviceKeysByReportTime( + LocalDateTime.now().minus(OFFLINE_TIMEOUT)); + + // 2. 下线设备 + List offlineDeviceKeys = CollUtil.newArrayList(); + for (IotDeviceDO device : devices) { + if (!timeoutDeviceKeys.contains(device.getDeviceKey())) { + continue; + } + offlineDeviceKeys.add(device.getDeviceKey()); + // 为什么不直接更新状态呢?因为通过 IotDeviceMessage 可以经过一系列的处理,例如说记录日志等等 + deviceUpstreamService.updateDeviceState(((IotDeviceStateUpdateReqDTO) + new IotDeviceStateUpdateReqDTO().setRequestId(IdUtil.fastSimpleUUID()).setReportTime(LocalDateTime.now()) + .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName())) + .setState((IotDeviceStateEnum.OFFLINE.getState()))); + } + return JsonUtils.toJsonString(offlineDeviceKeys); } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java index 0e46733bf8..8c89ed9fa6 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java +++ b/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 { PageResult getDevicePage(IotDevicePageReqVO pageReqVO); /** - * 获得设备列表 + * 基于设备类型,获得设备列表 * * @param deviceType 设备类型 * @return 设备列表 */ - List getDeviceList(@Nullable Integer deviceType); + List getDeviceListByDeviceType(@Nullable Integer deviceType); /** - * 获得设备数量 + * 获得状态,获得设备列表 + * + * @param state 状态 + * @return 设备列表 + */ + List getDeviceListByState(Integer state); + + /** + * 基于产品编号,获得设备数量 * * @param productId 产品编号 * @return 设备数量 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java index 9376a3f115..f4f53dfb6a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java +++ b/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 { } @Override - public List getDeviceList(@Nullable Integer deviceType) { - return deviceMapper.selectList(deviceType); + public List getDeviceListByDeviceType(@Nullable Integer deviceType) { + return deviceMapper.selectListByDeviceType(deviceType); + } + + @Override + public List getDeviceListByState(Integer state) { + return deviceMapper.selectListByState(state); } @Override diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyService.java index 0711230aab..ac23c1de15 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyService.java +++ b/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; import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; import jakarta.validation.Valid; +import java.time.LocalDateTime; import java.util.Map; +import java.util.Set; /** * IoT 设备【属性】数据 Service 接口 @@ -16,6 +18,8 @@ import java.util.Map; */ public interface IotDevicePropertyService { + // ========== 设备属性相关操作 ========== + /** * 定义设备属性数据的结构 * @@ -46,4 +50,22 @@ public interface IotDevicePropertyService { */ PageResult getHistoryDevicePropertyPage(@Valid IotDevicePropertyHistoryPageReqVO pageReqVO); + // ========== 设备时间相关操作 ========== + + /** + * 获得最后上报时间小于指定时间的设备标识 + * + * @param maxReportTime 最大上报时间 + * @return 设备标识列表 + */ + Set getDeviceKeysByReportTime(LocalDateTime maxReportTime); + + /** + * 更新设备上报时间 + * + * @param deviceKey 设备标识 + * @param reportTime 上报时间 + */ + void updateDeviceReportTime(String deviceKey, LocalDateTime reportTime); + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyServiceImpl.java index c4a3254c8c..9bcdde0987 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyServiceImpl.java +++ b/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; 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.dal.redis.device.DevicePropertyRedisDAO; +import cn.iocoder.yudao.module.iot.dal.redis.device.DeviceReportTimeRedisDAO; import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyMapper; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; @@ -28,10 +29,8 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.time.LocalDateTime; +import java.util.*; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; @@ -68,10 +67,14 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { @Resource private DevicePropertyRedisDAO deviceDataRedisDAO; + @Resource + private DeviceReportTimeRedisDAO deviceReportTimeRedisDAO; @Resource private IotDevicePropertyMapper devicePropertyMapper; + // ========== 设备属性相关操作 ========== + @Override public void defineDevicePropertyData(Long productId) { // 1.1 查询产品和物模型 @@ -179,4 +182,16 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { } } + // ========== 设备时间相关操作 ========== + + @Override + public Set getDeviceKeysByReportTime(LocalDateTime maxReportTime) { + return deviceReportTimeRedisDAO.range(maxReportTime); + } + + @Override + public void updateDeviceReportTime(String deviceKey, LocalDateTime reportTime) { + deviceReportTimeRedisDAO.update(deviceKey, reportTime); + } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/upstream/IotDeviceUpstreamServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/upstream/IotDeviceUpstreamServiceImpl.java index e27e3eb2c1..2585127697 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/upstream/IotDeviceUpstreamServiceImpl.java +++ b/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; import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer; import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; +import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -38,6 +39,8 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { @Resource private IotDeviceService deviceService; + @Resource + private IotDevicePropertyService devicePropertyService; @Resource private IotDeviceProducer deviceProducer; @@ -107,9 +110,11 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { // TODO 芋艿:待实现 } - private void updateDeviceLastTime(IotDeviceDO deviceDO, IotDeviceUpstreamAbstractReqDTO reqDTO) { - // TODO 芋艿:插件状态 - // TODO 芋艿:操作时间 + private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) { + // 1. TODO 芋艿:插件状态 + + // 2. 更新设备的最后时间 + devicePropertyService.updateDeviceReportTime(device.getDeviceKey(), LocalDateTime.now()); } private void sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device) {