package com.ycwl.basic.task; import com.fasterxml.jackson.databind.ObjectMapper; import com.ycwl.basic.device.DeviceFactory; import com.ycwl.basic.device.entity.common.DeviceVideoContinuityCache; import com.ycwl.basic.device.entity.common.VideoContinuityResult; import com.ycwl.basic.device.operator.IDeviceStorageOperator; import com.ycwl.basic.integration.common.response.PageResponse; import com.ycwl.basic.integration.device.dto.device.DeviceV2DTO; import com.ycwl.basic.integration.device.service.DeviceIntegrationService; import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity; import com.ycwl.basic.repository.DeviceRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.Calendar; import java.util.Date; import java.util.concurrent.TimeUnit; /** * 设备视频连续性检查定时任务 * - 仅在生产环境(prod)运行 * - 每5分钟执行一次 * - 检查前12分钟到前2分钟的视频连续性 * - 仅在9点到18点之间检查 * - 结果缓存到Redis中 * * @author Claude Code * @date 2025-09-01 */ @Slf4j @Component @EnableScheduling //@Profile("prod") public class DeviceVideoContinuityCheckTask { private static final String REDIS_KEY_PREFIX = "device:video:continuity:"; private static final int CACHE_TTL_HOURS = 24; // 缓存24小时 private static final int START_HOUR = 9; // 开始检查时间 9:00 private static final int END_HOUR = 18; // 结束检查时间 18:00 @Autowired private DeviceIntegrationService deviceIntegrationService; @Autowired private DeviceRepository deviceRepository; @Autowired private RedisTemplate redisTemplate; @Autowired private ObjectMapper objectMapper; /** * 定时任务:每5分钟执行一次 * cron表达式: 0 0/5 * * * * 表示每5分钟执行一次 */ @Scheduled(cron = "0 0/5 * * * *") public void checkDeviceVideoContinuity() { // 检查是否在执行时间范围内(9:00-18:00) int currentHour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); if (currentHour < START_HOUR || currentHour >= END_HOUR) { log.debug("当前时间 {}:00 不在检查时间范围内({}:00-{}:00),跳过检查", currentHour, START_HOUR, END_HOUR); return; } log.info("开始执行设备视频连续性检查定时任务"); long startTime = System.currentTimeMillis(); try { // 获取所有激活的设备(分页获取,每次100个) int pageSize = 100; int currentPage = 1; int totalChecked = 0; int successCount = 0; int failureCount = 0; while (true) { PageResponse pageResponse = deviceIntegrationService.listDevices( currentPage, pageSize, null, null, null, 1, null ); if (pageResponse == null || pageResponse.getList() == null || pageResponse.getList().isEmpty()) { break; } // 检查每个设备的视频连续性 for (DeviceV2DTO device : pageResponse.getList()) { try { boolean checked = checkSingleDevice(device); totalChecked++; if (checked) { successCount++; } else { failureCount++; } } catch (Exception e) { log.error("检查设备 {} 视频连续性失败: {}", device.getId(), e.getMessage(), e); failureCount++; totalChecked++; } } // 检查是否还有更多页 int totalPages = (int) Math.ceil((double) pageResponse.getTotal() / pageSize); if (currentPage >= totalPages) { break; } currentPage++; } long endTime = System.currentTimeMillis(); log.info("设备视频连续性检查任务完成: 总计检查 {} 个设备, 成功 {}, 失败 {}, 耗时 {}ms", totalChecked, successCount, failureCount, (endTime - startTime)); } catch (Exception e) { log.error("执行设备视频连续性检查定时任务失败", e); } } /** * 检查单个设备的视频连续性 * * @param device 设备信息 * @return true表示检查成功并缓存,false表示跳过检查 */ private boolean checkSingleDevice(DeviceV2DTO device) { try { // 获取设备配置 DeviceConfigEntity config = deviceRepository.getDeviceConfig(device.getId()); if (config == null) { log.debug("设备 {} 没有配置信息,跳过检查", device.getId()); return false; } // 获取设备的存储操作器 IDeviceStorageOperator operator = DeviceFactory.getDeviceStorageOperator(device, config); if (operator == null) { log.debug("设备 {} 没有配置存储操作器,跳过检查", device.getId()); return false; } // 计算检查时间范围: 当前时间向前12分钟到向前2分钟 Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MINUTE, -2); Date endDate = calendar.getTime(); calendar.add(Calendar.MINUTE, -10); // 再向前10分钟,总共12分钟 Date startDate = calendar.getTime(); // 执行连续性检查(允许2秒间隙) VideoContinuityResult result = operator.checkVideoContinuity(startDate, endDate, 2000L); // 创建缓存对象 DeviceVideoContinuityCache cache = DeviceVideoContinuityCache.fromResult( device.getId(), result, startDate, endDate ); // 存储到Redis String redisKey = REDIS_KEY_PREFIX + device.getId(); String cacheJson = objectMapper.writeValueAsString(cache); redisTemplate.opsForValue().set(redisKey, cacheJson, CACHE_TTL_HOURS, TimeUnit.HOURS); log.info("设备 {} 视频连续性检查完成: support={}, continuous={}, videos={}, gaps={}, duration={}ms", device.getId(), result.isSupport(), result.isContinuous(), result.getTotalVideos(), result.getGapCount(), result.getTotalDurationMs()); return true; } catch (Exception e) { log.error("检查设备 {} 视频连续性失败", device.getId(), e); throw new RuntimeException("检查设备视频连续性失败", e); } } /** * 手动触发检查(用于测试) * * @param deviceId 设备ID * @return 检查结果 */ public DeviceVideoContinuityCache manualCheck(Long deviceId) { log.info("手动触发设备 {} 的视频连续性检查", deviceId); try { // 获取设备信息 DeviceV2DTO device = deviceIntegrationService.getDevice(deviceId); if (device == null) { throw new RuntimeException("设备不存在: " + deviceId); } // 检查设备 checkSingleDevice(device); // 从Redis获取结果 String redisKey = REDIS_KEY_PREFIX + deviceId; String cacheJson = redisTemplate.opsForValue().get(redisKey); if (cacheJson == null) { throw new RuntimeException("检查完成但未找到缓存结果"); } return objectMapper.readValue(cacheJson, DeviceVideoContinuityCache.class); } catch (Exception e) { log.error("手动检查设备 {} 视频连续性失败", deviceId, e); throw new RuntimeException("手动检查失败: " + e.getMessage(), e); } } }