From 85599aa84a5df6daa42596ad53c7fb5c07810c87 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 30 Dec 2025 10:35:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(pc):=20=E6=B7=BB=E5=8A=A0=E5=A4=96?= =?UTF-8?q?=E9=83=A8=E5=B7=A5=E5=85=B7=E4=B8=8A=E6=8A=A5=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E8=BF=9E=E7=BB=AD=E6=80=A7=E6=A3=80=E6=9F=A5=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 @IgnoreToken 注解支持无需认证的上报功能 - 新增 reportContinuityResult 方法处理外部工具上报请求 - 实现根据设备编号查询设备ID的逻辑 - 添加视频连续性检查结果的缓存存储功能 - 支持间隙信息的转换和存储到Redis - 设置24小时缓存TTL策略 - 完善日志记录和异常处理机制 --- .../pc/DeviceVideoContinuityController.java | 80 +++++++++++++ .../device/req/VideoContinuityReportReq.java | 109 ++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 src/main/java/com/ycwl/basic/model/pc/device/req/VideoContinuityReportReq.java diff --git a/src/main/java/com/ycwl/basic/controller/pc/DeviceVideoContinuityController.java b/src/main/java/com/ycwl/basic/controller/pc/DeviceVideoContinuityController.java index cb9203f0..1affd237 100644 --- a/src/main/java/com/ycwl/basic/controller/pc/DeviceVideoContinuityController.java +++ b/src/main/java/com/ycwl/basic/controller/pc/DeviceVideoContinuityController.java @@ -1,14 +1,24 @@ package com.ycwl.basic.controller.pc; import com.fasterxml.jackson.databind.ObjectMapper; +import com.ycwl.basic.annotation.IgnoreToken; import com.ycwl.basic.device.entity.common.DeviceVideoContinuityCache; +import com.ycwl.basic.model.pc.device.entity.DeviceEntity; +import com.ycwl.basic.model.pc.device.req.VideoContinuityReportReq; +import com.ycwl.basic.repository.DeviceRepository; import com.ycwl.basic.task.DeviceVideoContinuityCheckTask; import com.ycwl.basic.utils.ApiResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + /** * 设备视频连续性检查控制器 * 提供查询设备视频连续性检查结果的接口 @@ -23,10 +33,12 @@ import org.springframework.web.bind.annotation.*; public class DeviceVideoContinuityController { private static final String REDIS_KEY_PREFIX = "device:video:continuity:"; + private static final int CACHE_TTL_HOURS = 24; // 缓存24小时 private final RedisTemplate redisTemplate; private final ObjectMapper objectMapper; private final DeviceVideoContinuityCheckTask checkTask; + private final DeviceRepository deviceRepository; /** * 查询设备最近的视频连续性检查结果 @@ -103,4 +115,72 @@ public class DeviceVideoContinuityController { return ApiResponse.buildResponse(500, null, "删除失败: " + e.getMessage()); } } + + /** + * 外部工具上报视频连续性检查结果 + * 通过设备编号(deviceNo)上报检查结果,无需认证 + * + * @param reportReq 上报请求 + * @return 上报结果 + */ + @PostMapping("/report") + @IgnoreToken + public ApiResponse reportContinuityResult( + @Validated @RequestBody VideoContinuityReportReq reportReq) { + log.info("外部工具上报设备 {} 的视频连续性检查结果", reportReq.getDeviceNo()); + + try { + // 1. 根据设备编号查询设备ID + DeviceEntity device = deviceRepository.getDeviceByDeviceNo(reportReq.getDeviceNo()); + if (device == null) { + log.warn("设备编号 {} 不存在", reportReq.getDeviceNo()); + return ApiResponse.buildResponse(404, null, "设备不存在: " + reportReq.getDeviceNo()); + } + + Long deviceId = device.getId(); + + // 2. 构建缓存对象 + DeviceVideoContinuityCache cache = new DeviceVideoContinuityCache(); + cache.setDeviceId(deviceId); + cache.setCheckTime(new Date()); + cache.setStartTime(reportReq.getStartTime()); + cache.setEndTime(reportReq.getEndTime()); + cache.setSupport(reportReq.getSupport()); + cache.setContinuous(reportReq.getContinuous()); + cache.setTotalVideos(reportReq.getTotalVideos()); + cache.setTotalDurationMs(reportReq.getTotalDurationMs()); + cache.setMaxAllowedGapMs(reportReq.getMaxAllowedGapMs() != null + ? reportReq.getMaxAllowedGapMs() : 2000L); + cache.setGapCount(reportReq.getGaps() != null ? reportReq.getGaps().size() : 0); + + // 3. 转换间隙信息 + if (reportReq.getGaps() != null && !reportReq.getGaps().isEmpty()) { + List gapInfos = reportReq.getGaps().stream() + .map(gap -> new DeviceVideoContinuityCache.GapInfo( + gap.getBeforeFileName(), + gap.getAfterFileName(), + gap.getGapMs(), + gap.getGapStartTime(), + gap.getGapEndTime() + )) + .collect(Collectors.toList()); + cache.setGaps(gapInfos); + } + + // 4. 存储到Redis + String redisKey = REDIS_KEY_PREFIX + deviceId; + String cacheJson = objectMapper.writeValueAsString(cache); + redisTemplate.opsForValue().set(redisKey, cacheJson, CACHE_TTL_HOURS, TimeUnit.HOURS); + + log.info("设备 {} (ID: {}) 视频连续性检查结果上报成功: continuous={}, videos={}, gaps={}", + reportReq.getDeviceNo(), deviceId, cache.getContinuous(), + cache.getTotalVideos(), cache.getGapCount()); + + return ApiResponse.success(cache); + + } catch (Exception e) { + log.error("外部工具上报设备 {} 视频连续性检查结果失败", reportReq.getDeviceNo(), e); + return ApiResponse.buildResponse(500, null, "上报失败: " + e.getMessage()); + } + } } diff --git a/src/main/java/com/ycwl/basic/model/pc/device/req/VideoContinuityReportReq.java b/src/main/java/com/ycwl/basic/model/pc/device/req/VideoContinuityReportReq.java new file mode 100644 index 00000000..09c6a6f1 --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/pc/device/req/VideoContinuityReportReq.java @@ -0,0 +1,109 @@ +package com.ycwl.basic.model.pc.device.req; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.Date; +import java.util.List; + +/** + * 外部工具上报视频连续性检查结果的请求DTO + * + * @author Claude Code + * @date 2025-12-30 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class VideoContinuityReportReq { + + /** + * 设备编号(必填) + */ + @NotBlank(message = "设备编号不能为空") + private String deviceNo; + + /** + * 检查的开始时间(必填) + */ + @NotNull(message = "检查开始时间不能为空") + private Date startTime; + + /** + * 检查的结束时间(必填) + */ + @NotNull(message = "检查结束时间不能为空") + private Date endTime; + + /** + * 是否支持连续性检查(必填) + */ + @NotNull(message = "是否支持连续性检查不能为空") + private Boolean support; + + /** + * 视频是否连续(必填) + * true: 所有间隙都在允许范围内 + * false: 存在超出允许范围的间隙 + */ + @NotNull(message = "视频是否连续不能为空") + private Boolean continuous; + + /** + * 视频总数(必填) + */ + @NotNull(message = "视频总数不能为空") + private Integer totalVideos; + + /** + * 总时长,单位毫秒(必填) + */ + @NotNull(message = "总时长不能为空") + private Long totalDurationMs; + + /** + * 允许的最大间隙,单位毫秒(选填,默认2000ms) + */ + private Long maxAllowedGapMs; + + /** + * 间隙列表(选填,当continuous=false时应提供) + */ + private List gaps; + + /** + * 间隙信息 + */ + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class GapInfoReq { + /** + * 前一个文件名 + */ + private String beforeFileName; + + /** + * 后一个文件名 + */ + private String afterFileName; + + /** + * 间隙时长,单位毫秒 + */ + private Long gapMs; + + /** + * 间隙开始时间 + */ + private Date gapStartTime; + + /** + * 间隙结束时间 + */ + private Date gapEndTime; + } +}