Commit 39aaeaa2983a1e7bfaf5a5198e5b2f32b4f4793e

Authored by YunaiV
1 parent eb74f753

【功能新增】IoT:增加 IotDeviceOfflineCheckJob,处理设备超时下线

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,7 +122,7 @@ public class IotDeviceController {
122 @Parameter(name = "deviceType", description = "设备类型", example = "1") 122 @Parameter(name = "deviceType", description = "设备类型", example = "1")
123 public CommonResult<List<IotDeviceRespVO>> getSimpleDeviceList( 123 public CommonResult<List<IotDeviceRespVO>> getSimpleDeviceList(
124 @RequestParam(value = "deviceType", required = false) Integer deviceType) { 124 @RequestParam(value = "deviceType", required = false) Integer deviceType) {
125 - List<IotDeviceDO> list = deviceService.getDeviceList(deviceType); 125 + List<IotDeviceDO> list = deviceService.getDeviceListByDeviceType(deviceType);
126 return success(convertList(list, device -> // 只返回 id、name 字段 126 return success(convertList(list, device -> // 只返回 id、name 字段
127 new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName()))); 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&lt;IotDeviceDO&gt; { @@ -52,10 +52,14 @@ public interface IotDeviceMapper extends BaseMapperX&lt;IotDeviceDO&gt; {
52 .apply("LOWER(device_key) = {0}", deviceKey.toLowerCase())); 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 return selectList(IotDeviceDO::getDeviceType, deviceType); 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 default Long selectCountByGroupId(Long groupId) { 63 default Long selectCountByGroupId(Long groupId) {
60 return selectCount(new LambdaQueryWrapperX<IotDeviceDO>() 64 return selectCount(new LambdaQueryWrapperX<IotDeviceDO>()
61 .apply("FIND_IN_SET(" + groupId + ",group_ids) > 0") 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,7 +24,7 @@ public interface RedisKeyConstants {
24 * KEY 格式:{deviceKey} 24 * KEY 格式:{deviceKey}
25 * SCORE:上报时间 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 * 设备信息的数据缓存,使用 Spring Cache 操作 30 * 设备信息的数据缓存,使用 Spring Cache 操作
@@ -32,7 +32,7 @@ public interface RedisKeyConstants { @@ -32,7 +32,7 @@ public interface RedisKeyConstants {
32 * KEY 格式:device_${productKey}_${deviceKey} 32 * KEY 格式:device_${productKey}_${deviceKey}
33 * VALUE 数据类型:String(JSON) 33 * VALUE 数据类型:String(JSON)
34 */ 34 */
35 - String DEVICE = "device"; 35 + String DEVICE = "device";
36 36
37 /** 37 /**
38 * 物模型的数据缓存,使用 Spring Cache 操作 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,6 +7,7 @@ import org.springframework.data.redis.core.StringRedisTemplate;
7 import org.springframework.stereotype.Repository; 7 import org.springframework.stereotype.Repository;
8 8
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
  10 +import java.util.Set;
10 11
11 /** 12 /**
12 * 设备的最后上报时间的 Redis DAO 13 * 设备的最后上报时间的 Redis DAO
@@ -20,8 +21,13 @@ public class DeviceReportTimeRedisDAO { @@ -20,8 +21,13 @@ public class DeviceReportTimeRedisDAO {
20 private StringRedisTemplate stringRedisTemplate; 21 private StringRedisTemplate stringRedisTemplate;
21 22
22 public void update(String deviceKey, LocalDateTime reportTime) { 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 LocalDateTimeUtil.toEpochMilli(reportTime)); 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 package cn.iocoder.yudao.module.iot.job.device; 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 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; 6 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
4 import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; 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 import org.springframework.stereotype.Component; 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 * IoT 设备离线检查 Job 24 * IoT 设备离线检查 Job
10 * 25 *
@@ -15,10 +30,46 @@ import org.springframework.stereotype.Component; @@ -15,10 +30,46 @@ import org.springframework.stereotype.Component;
15 @Component 30 @Component
16 public class IotDeviceOfflineCheckJob implements JobHandler { 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 @Override 47 @Override
19 @TenantJob 48 @TenantJob
20 public String execute(String param) { 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,15 +93,23 @@ public interface IotDeviceService {
93 PageResult<IotDeviceDO> getDevicePage(IotDevicePageReqVO pageReqVO); 93 PageResult<IotDeviceDO> getDevicePage(IotDevicePageReqVO pageReqVO);
94 94
95 /** 95 /**
96 - * 获得设备列表 96 + * 基于设备类型,获得设备列表
97 * 97 *
98 * @param deviceType 设备类型 98 * @param deviceType 设备类型
99 * @return 设备列表 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 * @param productId 产品编号 114 * @param productId 产品编号
107 * @return 设备数量 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,8 +226,13 @@ public class IotDeviceServiceImpl implements IotDeviceService {
226 } 226 }
227 227
228 @Override 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 @Override 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 +7,9 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
7 import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; 7 import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
8 import jakarta.validation.Valid; 8 import jakarta.validation.Valid;
9 9
  10 +import java.time.LocalDateTime;
10 import java.util.Map; 11 import java.util.Map;
  12 +import java.util.Set;
11 13
12 /** 14 /**
13 * IoT 设备【属性】数据 Service 接口 15 * IoT 设备【属性】数据 Service 接口
@@ -16,6 +18,8 @@ import java.util.Map; @@ -16,6 +18,8 @@ import java.util.Map;
16 */ 18 */
17 public interface IotDevicePropertyService { 19 public interface IotDevicePropertyService {
18 20
  21 + // ========== 设备属性相关操作 ==========
  22 +
19 /** 23 /**
20 * 定义设备属性数据的结构 24 * 定义设备属性数据的结构
21 * 25 *
@@ -46,4 +50,22 @@ public interface IotDevicePropertyService { @@ -46,4 +50,22 @@ public interface IotDevicePropertyService {
46 */ 50 */
47 PageResult<IotDevicePropertyRespVO> getHistoryDevicePropertyPage(@Valid IotDevicePropertyHistoryPageReqVO pageReqVO); 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 \ No newline at end of file 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,6 +14,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
14 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; 14 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
15 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; 15 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
16 import cn.iocoder.yudao.module.iot.dal.redis.device.DevicePropertyRedisDAO; 16 import cn.iocoder.yudao.module.iot.dal.redis.device.DevicePropertyRedisDAO;
  17 +import cn.iocoder.yudao.module.iot.dal.redis.device.DeviceReportTimeRedisDAO;
17 import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyMapper; 18 import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyMapper;
18 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; 19 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
19 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; 20 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
@@ -28,10 +29,8 @@ import jakarta.annotation.Resource; @@ -28,10 +29,8 @@ import jakarta.annotation.Resource;
28 import lombok.extern.slf4j.Slf4j; 29 import lombok.extern.slf4j.Slf4j;
29 import org.springframework.stereotype.Service; 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 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 35 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
37 36
@@ -68,10 +67,14 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { @@ -68,10 +67,14 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
68 67
69 @Resource 68 @Resource
70 private DevicePropertyRedisDAO deviceDataRedisDAO; 69 private DevicePropertyRedisDAO deviceDataRedisDAO;
  70 + @Resource
  71 + private DeviceReportTimeRedisDAO deviceReportTimeRedisDAO;
71 72
72 @Resource 73 @Resource
73 private IotDevicePropertyMapper devicePropertyMapper; 74 private IotDevicePropertyMapper devicePropertyMapper;
74 75
  76 + // ========== 设备属性相关操作 ==========
  77 +
75 @Override 78 @Override
76 public void defineDevicePropertyData(Long productId) { 79 public void defineDevicePropertyData(Long productId) {
77 // 1.1 查询产品和物模型 80 // 1.1 查询产品和物模型
@@ -179,4 +182,16 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { @@ -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 \ No newline at end of file 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,6 +18,7 @@ import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
18 import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; 18 import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
19 import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer; 19 import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer;
20 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 20 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
  21 +import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService;
21 import jakarta.annotation.Resource; 22 import jakarta.annotation.Resource;
22 import lombok.extern.slf4j.Slf4j; 23 import lombok.extern.slf4j.Slf4j;
23 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
@@ -38,6 +39,8 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { @@ -38,6 +39,8 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
38 39
39 @Resource 40 @Resource
40 private IotDeviceService deviceService; 41 private IotDeviceService deviceService;
  42 + @Resource
  43 + private IotDevicePropertyService devicePropertyService;
41 44
42 @Resource 45 @Resource
43 private IotDeviceProducer deviceProducer; 46 private IotDeviceProducer deviceProducer;
@@ -107,9 +110,11 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { @@ -107,9 +110,11 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
107 // TODO 芋艿:待实现 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 private void sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device) { 120 private void sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device) {