Преглед на файлове

fix: 设备在线显示问题

jackzhou преди 5 месеца
родител
ревизия
ff6b0913ca

+ 24 - 55
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/offline/DeviceOfflineDetectionService.java

@@ -12,6 +12,7 @@ import vip.xiaonuo.coldchain.core.alarm.config.ColdChainAlarmMessageProperties;
 import vip.xiaonuo.coldchain.core.alarm.enums.SensorAlarmType;
 import vip.xiaonuo.coldchain.core.event.SensorAlarmEvent;
 import vip.xiaonuo.coldchain.core.renke.config.JfcloudColdChainServerProperties;
+import vip.xiaonuo.coldchain.modular.monitordevice.service.MonitorDeviceService;
 import vip.xiaonuo.coldchain.modular.monitortarget.enums.MonitorStatusEnum;
 import vip.xiaonuo.coldchain.modular.monitortarget.service.MonitorTargetService;
 import vip.xiaonuo.coldchain.modular.monitortargetregion.entity.MonitorTargetRegion;
@@ -42,6 +43,7 @@ public class DeviceOfflineDetectionService {
     private final ApplicationEventPublisher applicationEventPublisher;
     private final MonitorTargetRegionService monitorTargetRegionService;
     private final MonitorTargetService monitorTargetService;
+    private final MonitorDeviceService monitorDeviceService;
     private final ColdChainAlarmMessageProperties alarmMessageProperties;
     private final JfcloudColdChainServerProperties jfcloudColdChainServerProperties;
     private static final String KEY_SPILT = "-";
@@ -63,71 +65,38 @@ public class DeviceOfflineDetectionService {
 
     /**
      * 定期检测设备状态是否离线
-     * 每 5 分钟运行一次
+     * 每 3 分钟运行一次
      */
-//    @Scheduled(fixedRate = 300000) // 每 5 分钟(以毫秒为单位)
-////    @Scheduled(fixedRateString = "${coldchain.device-offline-interval:300000}")
-//    public void checkDeviceStatus() {
-//        log.info("开始自动检测设备在线状态...");
-//        // 获取 Redis 中所有设备的键
-//        Set<String> deviceKeys = getAllDeviceCodes();
-//        Map<String,Integer> deviceCodes = new LinkedHashMap<>();
-//        if(deviceKeys!=null && !deviceKeys.isEmpty()){
-//            // 遍历设备键,逐一检测状态
-//            for (String key : deviceKeys) {
-//                String[] split = key.split(KEY_SPILT);
-//                if (split.length == 0) {
-//                    continue;
-//                }
-//                String deviceCode = split[0];
-//                Integer route = null;
-//                if (split.length == 2) {
-//                    route = Integer.valueOf(split[1]);
-//                }
-//                deviceCodes.put(deviceCode,route);
-//                // 从 Redis 中获取设备的最后上报时间
-//                String lastReportTimeStr = getLastDeviceTime(getKey(deviceCode, route));
-//                if (lastReportTimeStr != null) {
-//                    // 将上报时间字符串解析为 LocalDateTime 对象
-//                    LocalDateTime lastReportTime = LocalDateTime.parse(lastReportTimeStr, DATE_TIME_FORMATTER);
-//                    // 获取当前时间
-//                    LocalDateTime now = LocalDateTime.now();
-//                    final long OFFLINE_THRESHOLD_MINUTES = jfcloudColdChainServerProperties.getDeviceOfflineInterval() / 60 / 1000;
-//                    // 如果最后上报时间超过离线阈值,判断设备为离线
-//                    if (lastReportTime.plusMinutes(OFFLINE_THRESHOLD_MINUTES).isBefore(now)) {
-//                        log.error("设备{}-{} 已离线,最后上报时间:{}。\n", deviceCode, route, lastReportTimeStr);
-//                        publishAlarm(deviceCode, route, lastReportTimeStr);
-//                        monitorTargetService.updateStatusByDeviceCode(deviceCode, route, MonitorStatusEnum.OFF);
-//                    } else {
-//                        log.info("设备 {}-{} 在线,最后上报时间:{}。\n", deviceCode, route, lastReportTimeStr);
-//                        monitorTargetService.updateStatusByDeviceCode(deviceCode, route, MonitorStatusEnum.ONLINE);
-//                    }
-//                } else {
-//                    monitorTargetService.updateStatusByDeviceCode(deviceCode, route, MonitorStatusEnum.OFF);
-//                    log.info("设备 {}-{} 缺少数据,可能从未上报过。\n", deviceCode, route);
-//                }
-//            }
-//        }
-//        log.info("查找出从来没上线的设备都统一都下线处理");
-//        monitorTargetService.updateStatusbatch(deviceCodes);
-//    }
-
-    @Scheduled(fixedRate = 300000, initialDelay = 60000) // 每 5 分钟运行一次
+    @Scheduled(fixedRate = 180000, initialDelay = 60000) // 每 3 分钟运行一次
     public void checkDeviceStatus() {
         log.info("开始自动检测设备在线状态...");
         Map<String, Integer> deviceCodes = new LinkedHashMap<>();
-        Set<String> deviceKeys = getAllDeviceCodes();
-        if(deviceKeys!=null && deviceKeys.size()>0){
+        // 获取 Redis 中的设备集合
+        Set<String> sensorCodes = new HashSet<>();
+        Set<String> redisDeviceKeys = getAllRedisDeviceCodes();
+        if (redisDeviceKeys != null && !redisDeviceKeys.isEmpty()) {
+            Set<String> sensorCodes1 = redisDeviceKeys.stream()
+                    .map(key -> key.split(KEY_SPILT)[0]) // 提取出 sensorCode
+                    .collect(Collectors.toSet());
+            sensorCodes.addAll(sensorCodes1);
             LocalDateTime now = LocalDateTime.now();
             final long OFFLINE_THRESHOLD_MINUTES = jfcloudColdChainServerProperties.getDeviceOfflineInterval() / 60 / 1000;
-            for (String key : deviceKeys) {
+            // 处理每个设备
+            for (String key : redisDeviceKeys) {
                 processDevice(key, now, OFFLINE_THRESHOLD_MINUTES, deviceCodes);
             }
         }
+        // 获取所有设备代码
+        Set<String> allSensorCodes = monitorDeviceService.getAllDeviceCodes();
+        // 创建新的集合,避免修改原始集合
+        Set<String> offlineSensorCodes = new HashSet<>(allSensorCodes);
+        offlineSensorCodes.removeAll(sensorCodes); // 剩下的是未上线的设备
         log.info("查找出从来没上线的设备,统一设置为离线...");
-        monitorTargetService.updateStatusbatch(deviceCodes);
+        monitorTargetService.updateStatusbatch(offlineSensorCodes); // 更新离线设备状态
     }
 
+
+
     /**
      * 处理设备状态
      * @param key
@@ -135,7 +104,7 @@ public class DeviceOfflineDetectionService {
      * @param offlineThresholdMinutes
      * @param deviceCodes
      */
-    private void processDevice(String key, LocalDateTime now, long offlineThresholdMinutes, Map<String, Integer> deviceCodes) {
+    public void processDevice(String key, LocalDateTime now, long offlineThresholdMinutes, Map<String, Integer> deviceCodes) {
         String[] split = key.split(KEY_SPILT);
         if (split.length == 0) {
             return;
@@ -259,7 +228,7 @@ public class DeviceOfflineDetectionService {
         return commonCacheOperator.get(key) != null;
     }
 
-    private Set<String> getAllDeviceCodes() {
+    private Set<String> getAllRedisDeviceCodes() {
         return commonCacheOperator.getAllKeys().stream().filter(key -> key.startsWith(CACHE_KEY_PREFIX)) // 过滤指定前缀的键
                 .map(key -> key.replace(CACHE_KEY_PREFIX, "")) // 去掉前缀
                 .collect(Collectors.toSet());

+ 2 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/config/JfcloudColdChainConstants.java

@@ -38,6 +38,8 @@ public interface JfcloudColdChainConstants {
     String MONITORTARGETREGION_CACHE_NAME = "monitorTargetRegion";
     String MONITORTARGET_CACHE_NAME = "monitorTarget";
 
+    String MONITORDEVICE_CACHE_NAME = "monitorDevice";
+
     /**
      * 数据上报队列最大容量
      */

+ 13 - 3
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/config/JfcloudRedisCacheService.java

@@ -7,6 +7,7 @@ package vip.xiaonuo.coldchain.core.config;
  * @description
  * @date 2025/1/6 12:48:37
  */
+
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.Cache;
@@ -33,10 +34,19 @@ public class JfcloudRedisCacheService {
         // 获取指定缓存
         Cache cache = cacheManager.getCache(JfcloudColdChainConstants.MONITORTARGET_CACHE_NAME);
         if (cache != null) {
-            String key = orgId;
-            cache.evict(key);
-            log.info("Cache evicted for key: " + key);
+            cache.evict(orgId);
+            log.info("Cache evicted for key: " + orgId);
         }
     }
+
+    public void evictMonitorDeviceCache(){
+        // 获取指定缓存
+        Cache cache = cacheManager.getCache(JfcloudColdChainConstants.MONITORDEVICE_CACHE_NAME);
+        if (cache != null) {
+            cache.clear();
+            log.info("All caches have been evicted for cache name: " + JfcloudColdChainConstants.MONITORDEVICE_CACHE_NAME);
+        }
+    }
+
 }
 

+ 3 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitordevice/service/MonitorDeviceService.java

@@ -31,6 +31,7 @@ import vip.xiaonuo.coldchain.modular.monitordevicetype.entity.CountEntity;
 import java.time.Instant;
 import java.util.Date;
 import java.util.List;
+import java.util.Set;
 
 /**
  * 采集器管理Service接口
@@ -129,4 +130,6 @@ public interface MonitorDeviceService extends IService<MonitorDevice> {
      * @return
      */
     void importMonitorDevice(MultipartFile file);
+
+    Set<String> getAllDeviceCodes();
 }

+ 22 - 5
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitordevice/service/impl/MonitorDeviceServiceImpl.java

@@ -18,8 +18,6 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import com.alibaba.excel.EasyExcel;
 import com.alibaba.excel.support.ExcelTypeEnum;
-import com.alibaba.excel.write.metadata.style.WriteCellStyle;
-import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -30,7 +28,7 @@ import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
@@ -40,6 +38,8 @@ import vip.xiaonuo.auth.core.pojo.SaBaseLoginUser;
 import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
 import vip.xiaonuo.coldchain.core.bean.influxdb.SensorData;
 import vip.xiaonuo.coldchain.core.cache.monitordevice.MonitorDeviceCache;
+import vip.xiaonuo.coldchain.core.config.JfcloudColdChainConstants;
+import vip.xiaonuo.coldchain.core.config.JfcloudRedisCacheService;
 import vip.xiaonuo.coldchain.core.renke.RenKeService;
 import vip.xiaonuo.coldchain.core.service.JfcloudSensorDataService;
 import vip.xiaonuo.coldchain.modular.monitordevice.dto.MonitorDevicePageDto;
@@ -71,7 +71,6 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
-import java.text.ParseException;
 import java.time.Instant;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -96,7 +95,8 @@ public class MonitorDeviceServiceImpl extends ServiceImpl<MonitorDeviceMapper, M
     private MonitorTargetRegionService monitorTargetRegionService;
     @Resource
     private MonitorTargetService monitorTargetService;
-
+    @Resource
+    JfcloudRedisCacheService jfcloudRedisCacheService;
     private String bucketName;
 
     @Override
@@ -188,6 +188,7 @@ public class MonitorDeviceServiceImpl extends ServiceImpl<MonitorDeviceMapper, M
         }
         //缓存整形的设备编号和设备型号到缓存
         monitorDeviceCache.cacheDevice(monitorDevice.getDeviceCode(), monitorDevice.getModelName());
+        jfcloudRedisCacheService.evictMonitorDeviceCache();
     }
 
     public boolean getByDeviceName(String deviceName) {
@@ -236,6 +237,7 @@ public class MonitorDeviceServiceImpl extends ServiceImpl<MonitorDeviceMapper, M
         if (update) {
             try {
                 // 调用设备配置参数接口
+                jfcloudRedisCacheService.evictMonitorDeviceCache();
                 renKeService.callParamIds(Integer.parseInt(monitorDevice.getDeviceCode()));
             } catch (NumberFormatException e) {
                 // 如果设备编码不能解析为整数,抛出异常或记录日志
@@ -260,6 +262,7 @@ public class MonitorDeviceServiceImpl extends ServiceImpl<MonitorDeviceMapper, M
         }
         // 执行删除
         this.removeByIds(CollStreamUtil.toList(monitorDeviceIdParamList, MonitorDeviceIdParam::getId));
+        jfcloudRedisCacheService.evictMonitorDeviceCache();
     }
 
     @Override
@@ -512,4 +515,18 @@ public class MonitorDeviceServiceImpl extends ServiceImpl<MonitorDeviceMapper, M
         monitorDeviceList.forEach(this::add);
     }
 
+    @Override
+    @Cacheable(value = JfcloudColdChainConstants.MONITORDEVICE_CACHE_NAME, unless = "#result == null")
+    public Set<String> getAllDeviceCodes() {
+        // 构建查询条件,查询 modelName 以 "RS" 开头的设备
+        LambdaQueryWrapper<MonitorDevice> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.likeRight(MonitorDevice::getModelName, "RS");
+        queryWrapper.select(MonitorDevice::getDeviceCode);  // 只查询 deviceCode 字段
+        List<MonitorDevice> deviceList = this.list(queryWrapper);
+        // 提取 deviceCode 字段并收集到 Set<String> 中
+        return deviceList.stream()
+                .map(MonitorDevice::getDeviceCode)  // 提取 deviceCode 字段
+                .collect(Collectors.toSet());      // 收集到 Set 中去重
+    }
+
 }

+ 1 - 2
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/mapper/mapping/MonitorTargetMapper.xml

@@ -79,6 +79,7 @@
             AND (r.sensor_code LIKE CONCAT('%', #{param.searchKey}, '%')
             OR r.name LIKE CONCAT('%', #{param.searchKey}, '%'))
         </if>
+        GROUP BY t.id
         <!-- 动态排序 -->
         <choose>
             <when test="param.sortField != null and param.sortField.trim() != '' and param.sortOrder != null and param.sortOrder.trim() != ''">
@@ -94,8 +95,6 @@
                 ORDER BY t.status ASC, t.name ASC
             </otherwise>
         </choose>
-<!--        &lt;!&ndash; 分页 &ndash;&gt;-->
-<!--        LIMIT #{param.offset}, #{param.size}-->
     </select>
 
 

+ 2 - 2
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/service/MonitorTargetService.java

@@ -21,7 +21,7 @@ import vip.xiaonuo.coldchain.modular.targetroom.entity.TargetRoom;
 
 import java.util.Date;
 import java.util.List;
-import java.util.Map;
+import java.util.Set;
 
 /**
  * 监控对象管理Service接口
@@ -142,7 +142,7 @@ public interface MonitorTargetService extends IService<MonitorTarget> {
 
     TargetRoom getRoomByMonitorTargetId(String monitorTargetId);
 
-    void updateStatusbatch(Map<String,Integer> deviceCodes);
+    void updateStatusbatch(Set<String> allSensorCodes);
 
     Page<MonitorTarget> selectMonitorTargetByPage(MonitorTargetPageParam monitorTargetPageParam);
 }

+ 8 - 13
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/service/impl/MonitorTargetServiceImpl.java

@@ -171,26 +171,21 @@ public class MonitorTargetServiceImpl extends ServiceImpl<MonitorTargetMapper, M
         return null;
     }
 
+    /**
+     * 批量更新离线设备
+     * @param allSensorCodes
+     */
     @Override
-    public void updateStatusbatch(Map<String, Integer> notInPairs) {
+    public void updateStatusbatch(Set<String> allSensorCodes) {
         // 构建查询建大仁科条件
         LambdaQueryWrapper<MonitorTargetRegion> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.likeRight(MonitorTargetRegion::getModelName, "RS");
-        if(!notInPairs.isEmpty()){
-            // 将每个 (deviceCode, sensorRoute) 组合转换为 "deviceCode:sensorRoute" 格式的字符串
-            List<String> notInConditions = notInPairs.entrySet().stream()
-                    .map(entry -> entry.getKey() + ":" + entry.getValue())
-                    .collect(Collectors.toList());
-            if(!notInConditions.isEmpty()){
-                queryWrapper.notIn(MonitorTargetRegion::getDeviceCodeAndSensorRoute, notInConditions);
-            }
-        }
-        // 查询不符合条件的 MonitorTargetRegion
+        queryWrapper.in(MonitorTargetRegion::getSensorCode,allSensorCodes);
+        queryWrapper.eq(MonitorTargetRegion::getDeleteFlag,CommonDeleteFlagEnum.NOT_DELETE);
         List<MonitorTargetRegion> targetRegions = monitorTargetRegionService.list(queryWrapper);
-        // 使用 stream 过滤出 deviceCode 不为空的 monitorTargetId 列表
         Set<String> monitorTargetIds = targetRegions.stream()
                 .filter(monitorTargetRegion -> monitorTargetRegion.getDeviceCode() != null)
-                .map(MonitorTargetRegion::getMonitorTargetId) // 提取 monitorTargetId
+                .map(MonitorTargetRegion::getMonitorTargetId)
                 .collect(Collectors.toSet());
         // 批量更新状态
         if (!monitorTargetIds.isEmpty()) {