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,155 @@
|
||||
package com.ycwl.basic.device.entity.common;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 设备视频连续性检查缓存实体
|
||||
* 用于存储在Redis中的检查结果
|
||||
*
|
||||
* @author Claude Code
|
||||
* @date 2025-09-01
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class DeviceVideoContinuityCache implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 设备ID
|
||||
*/
|
||||
private Long deviceId;
|
||||
|
||||
/**
|
||||
* 检查时间
|
||||
*/
|
||||
private Date checkTime;
|
||||
|
||||
/**
|
||||
* 检查的开始时间
|
||||
*/
|
||||
private Date startTime;
|
||||
|
||||
/**
|
||||
* 检查的结束时间
|
||||
*/
|
||||
private Date endTime;
|
||||
|
||||
/**
|
||||
* 是否支持连续性检查
|
||||
*/
|
||||
private Boolean support;
|
||||
|
||||
/**
|
||||
* 视频是否连续
|
||||
*/
|
||||
private Boolean continuous;
|
||||
|
||||
/**
|
||||
* 视频总数
|
||||
*/
|
||||
private Integer totalVideos;
|
||||
|
||||
/**
|
||||
* 总时长(毫秒)
|
||||
*/
|
||||
private Long totalDurationMs;
|
||||
|
||||
/**
|
||||
* 允许的最大间隙(毫秒)
|
||||
*/
|
||||
private Long maxAllowedGapMs;
|
||||
|
||||
/**
|
||||
* 间隙数量
|
||||
*/
|
||||
private Integer gapCount;
|
||||
|
||||
/**
|
||||
* 间隙列表(简化版,只包含关键信息)
|
||||
*/
|
||||
private List<GapInfo> gaps;
|
||||
|
||||
/**
|
||||
* 间隙信息简化类
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class GapInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 前一个文件名
|
||||
*/
|
||||
private String beforeFileName;
|
||||
|
||||
/**
|
||||
* 后一个文件名
|
||||
*/
|
||||
private String afterFileName;
|
||||
|
||||
/**
|
||||
* 间隙时长(毫秒)
|
||||
*/
|
||||
private Long gapMs;
|
||||
|
||||
/**
|
||||
* 间隙开始时间
|
||||
*/
|
||||
private Date gapStartTime;
|
||||
|
||||
/**
|
||||
* 间隙结束时间
|
||||
*/
|
||||
private Date gapEndTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从VideoContinuityResult创建缓存对象
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param result 检查结果
|
||||
* @param startTime 检查开始时间
|
||||
* @param endTime 检查结束时间
|
||||
* @return 缓存对象
|
||||
*/
|
||||
public static DeviceVideoContinuityCache fromResult(Long deviceId, VideoContinuityResult result,
|
||||
Date startTime, Date endTime) {
|
||||
DeviceVideoContinuityCache cache = new DeviceVideoContinuityCache();
|
||||
cache.setDeviceId(deviceId);
|
||||
cache.setCheckTime(new Date());
|
||||
cache.setStartTime(startTime);
|
||||
cache.setEndTime(endTime);
|
||||
cache.setSupport(result.isSupport());
|
||||
cache.setContinuous(result.isContinuous());
|
||||
cache.setTotalVideos(result.getTotalVideos());
|
||||
cache.setTotalDurationMs(result.getTotalDurationMs());
|
||||
cache.setMaxAllowedGapMs(result.getMaxAllowedGapMs());
|
||||
cache.setGapCount(result.getGapCount());
|
||||
|
||||
// 转换间隙列表
|
||||
if (result.getGaps() != null && !result.getGaps().isEmpty()) {
|
||||
List<GapInfo> gapInfos = result.getGaps().stream()
|
||||
.map(gap -> new GapInfo(
|
||||
gap.getBeforeFile() != null ? gap.getBeforeFile().getName() : null,
|
||||
gap.getAfterFile() != null ? gap.getAfterFile().getName() : null,
|
||||
gap.getGapMs(),
|
||||
gap.getGapStartTime(),
|
||||
gap.getGapEndTime()
|
||||
))
|
||||
.toList();
|
||||
cache.setGaps(gapInfos);
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.ycwl.basic.device.entity.common;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 视频连续性检查中的间隙信息
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class VideoContinuityGap {
|
||||
/**
|
||||
* 间隙前的视频文件
|
||||
*/
|
||||
private FileObject beforeFile;
|
||||
|
||||
/**
|
||||
* 间隙后的视频文件
|
||||
*/
|
||||
private FileObject afterFile;
|
||||
|
||||
/**
|
||||
* 间隙时长(毫秒)
|
||||
*/
|
||||
private long gapMs;
|
||||
|
||||
/**
|
||||
* 间隙开始时间(前一个视频的endTime)
|
||||
*/
|
||||
private Date gapStartTime;
|
||||
|
||||
/**
|
||||
* 间隙结束时间(后一个视频的createTime)
|
||||
*/
|
||||
private Date gapEndTime;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.ycwl.basic.device.entity.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 视频连续性检查结果
|
||||
*/
|
||||
@Data
|
||||
public class VideoContinuityResult {
|
||||
/**
|
||||
* 是否支持连续性检查功能
|
||||
*/
|
||||
private boolean support;
|
||||
|
||||
/**
|
||||
* 视频是否连续(所有间隙都在允许范围内)
|
||||
*/
|
||||
private boolean continuous;
|
||||
|
||||
/**
|
||||
* 检测到的间隙列表
|
||||
*/
|
||||
private List<VideoContinuityGap> gaps = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 视频文件总数
|
||||
*/
|
||||
private int totalVideos;
|
||||
|
||||
/**
|
||||
* 总时长(毫秒)
|
||||
*/
|
||||
private long totalDurationMs;
|
||||
|
||||
/**
|
||||
* 允许的最大间隙(毫秒)
|
||||
*/
|
||||
private long maxAllowedGapMs;
|
||||
|
||||
/**
|
||||
* 添加一个间隙
|
||||
*/
|
||||
public void addGap(VideoContinuityGap gap) {
|
||||
this.gaps.add(gap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取间隙数量
|
||||
*/
|
||||
public int getGapCount() {
|
||||
return gaps.size();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,50 @@
|
||||
package com.ycwl.basic.device.operator;
|
||||
|
||||
import com.ycwl.basic.device.entity.common.VideoContinuityResult;
|
||||
import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity;
|
||||
import com.ycwl.basic.model.pc.device.entity.DeviceEntity;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
public abstract class ADeviceStorageOperator implements IDeviceStorageOperator {
|
||||
@Setter
|
||||
protected DeviceEntity device;
|
||||
@Setter
|
||||
protected DeviceConfigEntity deviceConfig;
|
||||
|
||||
/**
|
||||
* 默认实现:不支持视频连续性检查
|
||||
*
|
||||
* @param startDate 开始时间
|
||||
* @param endDate 结束时间
|
||||
* @param maxGapMs 允许的最大间隔时间(毫秒)
|
||||
* @return support=false的结果
|
||||
*/
|
||||
@Override
|
||||
public VideoContinuityResult checkVideoContinuity(Date startDate, Date endDate, long maxGapMs) {
|
||||
VideoContinuityResult result = new VideoContinuityResult();
|
||||
result.setSupport(false);
|
||||
result.setContinuous(false);
|
||||
result.setTotalVideos(0);
|
||||
result.setTotalDurationMs(0);
|
||||
result.setMaxAllowedGapMs(maxGapMs);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认实现:不支持视频连续性检查
|
||||
*
|
||||
* @return support=false的结果
|
||||
*/
|
||||
@Override
|
||||
public VideoContinuityResult checkRecentVideoContinuity() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.MINUTE, -2);
|
||||
Date endDate = calendar.getTime();
|
||||
calendar.add(Calendar.MINUTE, -5);
|
||||
Date startDate = calendar.getTime();
|
||||
return checkVideoContinuity(startDate, endDate, 2000L);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.ycwl.basic.device.operator;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.ycwl.basic.utils.JacksonUtil;
|
||||
import com.ycwl.basic.device.entity.common.FileObject;
|
||||
import com.ycwl.basic.device.entity.common.VideoContinuityGap;
|
||||
import com.ycwl.basic.device.entity.common.VideoContinuityResult;
|
||||
import com.ycwl.basic.storage.StorageFactory;
|
||||
import com.ycwl.basic.storage.adapters.IStorageAdapter;
|
||||
import com.ycwl.basic.storage.entity.AliOssStorageConfig;
|
||||
@@ -98,4 +100,104 @@ public class AliOssStorageOperator extends ADeviceStorageOperator {
|
||||
String prefix = dateFormat.format(calendar.getTime());
|
||||
return removeFilesByPrefix(prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查视频片段的连续性
|
||||
*
|
||||
* @param startDate 开始时间
|
||||
* @param endDate 结束时间
|
||||
* @param maxGapMs 允许的最大间隔时间(毫秒)
|
||||
* @return 包含缺口信息的验证结果
|
||||
*/
|
||||
@Override
|
||||
public VideoContinuityResult checkVideoContinuity(Date startDate, Date endDate, long maxGapMs) {
|
||||
VideoContinuityResult result = new VideoContinuityResult();
|
||||
result.setSupport(true);
|
||||
result.setMaxAllowedGapMs(maxGapMs);
|
||||
|
||||
// 获取时间范围内的视频列表
|
||||
List<FileObject> fileList = getFileListByDtRange(startDate, endDate);
|
||||
|
||||
if (fileList == null || fileList.isEmpty()) {
|
||||
result.setContinuous(false);
|
||||
result.setTotalVideos(0);
|
||||
result.setTotalDurationMs(0);
|
||||
log.warn("未找到指定时间范围内的视频文件: {} - {}", startDate, endDate);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.setTotalVideos(fileList.size());
|
||||
|
||||
// 只有一个视频文件时,认为是连续的
|
||||
if (fileList.size() == 1) {
|
||||
FileObject file = fileList.get(0);
|
||||
long duration = file.getEndTime().getTime() - file.getCreateTime().getTime();
|
||||
result.setContinuous(true);
|
||||
result.setTotalDurationMs(duration);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 检查相邻视频之间的间隙
|
||||
long totalDuration = 0;
|
||||
for (int i = 0; i < fileList.size() - 1; i++) {
|
||||
FileObject currentFile = fileList.get(i);
|
||||
FileObject nextFile = fileList.get(i + 1);
|
||||
|
||||
// 计算当前视频的时长
|
||||
totalDuration += currentFile.getEndTime().getTime() - currentFile.getCreateTime().getTime();
|
||||
|
||||
// 计算间隙: 后一个视频的开始时间 - 前一个视频的结束时间
|
||||
long gapMs = nextFile.getCreateTime().getTime() - currentFile.getEndTime().getTime();
|
||||
|
||||
// 如果间隙超过允许值,记录该间隙
|
||||
if (gapMs > maxGapMs) {
|
||||
VideoContinuityGap gap = new VideoContinuityGap();
|
||||
gap.setBeforeFile(currentFile);
|
||||
gap.setAfterFile(nextFile);
|
||||
gap.setGapMs(gapMs);
|
||||
gap.setGapStartTime(currentFile.getEndTime());
|
||||
gap.setGapEndTime(nextFile.getCreateTime());
|
||||
result.addGap(gap);
|
||||
log.debug("检测到视频间隙: {} -> {}, 间隙时长: {}ms",
|
||||
currentFile.getName(), nextFile.getName(), gapMs);
|
||||
}
|
||||
}
|
||||
|
||||
// 加上最后一个视频的时长
|
||||
FileObject lastFile = fileList.get(fileList.size() - 1);
|
||||
totalDuration += lastFile.getEndTime().getTime() - lastFile.getCreateTime().getTime();
|
||||
|
||||
result.setTotalDurationMs(totalDuration);
|
||||
result.setContinuous(result.getGapCount() == 0);
|
||||
|
||||
log.info("视频连续性检查完成: 总视频数={}, 总时长={}ms, 间隙数={}, 连续={}",
|
||||
result.getTotalVideos(), result.getTotalDurationMs(), result.getGapCount(), result.isContinuous());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查近期视频的连续性(测试用)
|
||||
* 时间范围: 当前时间向前2分钟后,再向前10分钟(即前12分钟到前2分钟)
|
||||
* 允许的最大间隙: 2秒
|
||||
*
|
||||
* @return 包含缺口信息的验证结果
|
||||
*/
|
||||
@Override
|
||||
public VideoContinuityResult checkRecentVideoContinuity() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
|
||||
// 结束时间: 当前时间 - 2分钟
|
||||
calendar.add(Calendar.MINUTE, -2);
|
||||
Date endDate = calendar.getTime();
|
||||
|
||||
// 开始时间: 当前时间 - 12分钟 (再向前10分钟)
|
||||
calendar.add(Calendar.MINUTE, -10);
|
||||
Date startDate = calendar.getTime();
|
||||
|
||||
log.info("检查近期视频连续性: {} - {}", startDate, endDate);
|
||||
|
||||
// 允许的最大间隙为2秒(2000毫秒)
|
||||
return checkVideoContinuity(startDate, endDate, 2000L);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ycwl.basic.device.operator;
|
||||
|
||||
import com.ycwl.basic.device.IDeviceCommon;
|
||||
import com.ycwl.basic.device.entity.common.FileObject;
|
||||
import com.ycwl.basic.device.entity.common.VideoContinuityResult;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -19,10 +20,29 @@ public interface IDeviceStorageOperator extends IDeviceCommon {
|
||||
List<FileObject> getFileListByDtRange(Date startDate, Date endDate);
|
||||
|
||||
/**
|
||||
* 删除指定日期之前的文件,不包含指定的日期当天
|
||||
* 删除指定日期之前的文件,不包含指定的日期当天
|
||||
*
|
||||
* @param date 指定日期,不包含指定日期当天
|
||||
* @param date 指定日期,不包含指定日期当天
|
||||
* @return
|
||||
*/
|
||||
boolean removeFilesBeforeDate(Date date);
|
||||
|
||||
/**
|
||||
* 检查视频片段的连续性
|
||||
*
|
||||
* @param startDate 开始时间
|
||||
* @param endDate 结束时间
|
||||
* @param maxGapMs 允许的最大间隔时间(毫秒)
|
||||
* @return 包含缺口信息的验证结果
|
||||
*/
|
||||
VideoContinuityResult checkVideoContinuity(Date startDate, Date endDate, long maxGapMs);
|
||||
|
||||
/**
|
||||
* 检查近期视频的连续性(便捷方法)
|
||||
* 时间范围: 当前时间向前2分钟后,再向前5分钟(即前7分钟到前2分钟)
|
||||
* 允许的最大间隙: 2秒
|
||||
*
|
||||
* @return 包含缺口信息的验证结果
|
||||
*/
|
||||
VideoContinuityResult checkRecentVideoContinuity();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.ycwl.basic.device.operator;
|
||||
|
||||
import com.ycwl.basic.device.entity.common.FileObject;
|
||||
import com.ycwl.basic.device.entity.common.VideoContinuityResult;
|
||||
import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity;
|
||||
import com.ycwl.basic.model.pc.device.entity.DeviceEntity;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -34,4 +36,24 @@ public class LocalStorageOperator implements IDeviceStorageOperator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoContinuityResult checkVideoContinuity(Date startDate, Date endDate, long maxGapMs) {
|
||||
VideoContinuityResult result = new VideoContinuityResult();
|
||||
result.setSupport(false);
|
||||
result.setContinuous(false);
|
||||
result.setTotalVideos(0);
|
||||
result.setTotalDurationMs(0);
|
||||
result.setMaxAllowedGapMs(maxGapMs);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoContinuityResult checkRecentVideoContinuity() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.MINUTE, -2);
|
||||
Date endDate = calendar.getTime();
|
||||
calendar.add(Calendar.MINUTE, -5);
|
||||
Date startDate = calendar.getTime();
|
||||
return checkVideoContinuity(startDate, endDate, 2000L);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user