You've already forked FrameTour-BE
feat(device): 实现设备视频连续性检查功能
All checks were successful
ZhenTu-BE/pipeline/head This commit looks good
All checks were successful
ZhenTu-BE/pipeline/head This commit looks good
- 新增设备视频连续性检查控制器 DeviceVideoContinuityController - 提供查询、手动触发和删除检查结果的 REST 接口 - 实现视频连续性检查核心逻辑,支持检测视频间隙 - 添加定时任务 DeviceVideoContinuityCheckTask 自动检查设备视频连续性 - 仅在生产环境(prod)启用,每天9点到18点间每5分钟执行一次 - 支持阿里云OSS和本地存储的视频连续性检查 - 检查结果缓存至 Redis,默认保留24小时 - 新增相关实体类: DeviceVideoContinuityCache、VideoContinuityGap、VideoContinuityResult - 在存储操作接口中增加 checkVideoContinuity 和 checkRecentVideoContinuity 方法 - 为不支持的存储类型提供默认不支持连续性检查的实现
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
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<String, String> 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<DeviceV2DTO> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user