修复:截图代理下载验证 + HEAD请求支持 + 状态栏位置调整
后端修复: 1. proxyScreenshotImage 优化 - 直接从DB获取cos_key生成presigned URL - 移除过期的Redis缓存URL优先逻辑(避免使用过期URL) - 添加JPEG魔数验证,确保返回有效图片数据 - 空数据或无效数据返回null而非损坏数据 2. /snap/image 端点支持 HEAD 请求 - HEAD请求只检查图片是否存在,返回Content-Length - GET请求返回完整图片数据 - 用于前端快速检测截图可用性 前端修复: 1. 状态栏位置调整 - 列顺序改为:应用名 -> 流ID -> 状态 -> 拉流地址 -> ROI 2. 状态检测优化 - 使用 HEAD 请求替代 GET,避免下载完整图片 - 使用 fetch API 直接发送 HEAD 请求 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -117,7 +118,22 @@ public class AiRoiController {
|
||||
|
||||
@Operation(summary = "截图图片代理(服务端从 COS 下载后返回)")
|
||||
@GetMapping("/snap/image")
|
||||
public ResponseEntity<byte[]> getSnapImage(@RequestParam String cameraCode) {
|
||||
@RequestMapping(value = "/snap/image", method = {RequestMethod.GET, RequestMethod.HEAD})
|
||||
public ResponseEntity<?> getSnapImage(@RequestParam String cameraCode, HttpServletRequest request) {
|
||||
// HEAD 请求:只检查是否存在,不返回图片数据
|
||||
if ("HEAD".equalsIgnoreCase(request.getMethod())) {
|
||||
byte[] image = screenshotService.proxyScreenshotImage(cameraCode);
|
||||
if (image == null || image.length == 0) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_JPEG)
|
||||
.contentLength(image.length)
|
||||
.header("Cache-Control", "public, max-age=300")
|
||||
.build();
|
||||
}
|
||||
|
||||
// GET 请求:返回图片数据
|
||||
byte[] image = screenshotService.proxyScreenshotImage(cameraCode);
|
||||
if (image == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
|
||||
@@ -258,33 +258,14 @@ public class AiScreenshotServiceImpl implements IAiScreenshotService {
|
||||
|
||||
@Override
|
||||
public byte[] proxyScreenshotImage(String cameraCode) {
|
||||
// 1. 先查 Redis 缓存中的 presigned URL(5分钟有效)
|
||||
String cacheJson = stringRedisTemplate.opsForValue().get(SNAP_CACHE_KEY_PREFIX + cameraCode);
|
||||
if (cacheJson != null) {
|
||||
try {
|
||||
JSONObject cached = JSON.parseObject(cacheJson);
|
||||
String cosUrl = cached.getString("url");
|
||||
if (cosUrl != null && !cosUrl.isEmpty()) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
byte[] bytes = restTemplate.getForObject(URI.create(cosUrl), byte[].class);
|
||||
if (bytes != null && bytes.length > 0) {
|
||||
log.debug("[AI截图] 代理图片(Redis缓存): cameraCode={}, size={}", cameraCode, bytes.length);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[AI截图] Redis 缓存 URL 下载失败,尝试 DB: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 查 DB 持久化的 cos_key(永不过期)
|
||||
// 1. 查 DB 持久化的 cos_key(永不过期)- 优先直接操作 COS
|
||||
String cosKey = snapshotMapper.getCosKey(cameraCode);
|
||||
if (cosKey == null) {
|
||||
log.warn("[AI截图] 代理图片: 无缓存也无持久化记录 cameraCode={}", cameraCode);
|
||||
log.warn("[AI截图] 代理图片: 无持久化记录 cameraCode={}", cameraCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 通过 CosUtil 直接生成 presigned URL(无需调 FastAPI)
|
||||
// 2. 通过 CosUtil 直接生成 presigned URL
|
||||
if (!cosUtil.isAvailable()) {
|
||||
log.warn("[AI截图] COS 客户端未初始化,无法生成 presigned URL");
|
||||
return null;
|
||||
@@ -297,15 +278,24 @@ public class AiScreenshotServiceImpl implements IAiScreenshotService {
|
||||
}
|
||||
|
||||
try {
|
||||
// 4. 下载图片
|
||||
// 3. 下载图片
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
byte[] imageBytes = restTemplate.getForObject(URI.create(presignedUrl), byte[].class);
|
||||
|
||||
// 5. 更新 Redis 缓存(加速后续请求)
|
||||
if (imageBytes != null && imageBytes.length > 0) {
|
||||
writeCache(cameraCode, presignedUrl);
|
||||
log.debug("[AI截图] 代理图片(DB→COS): cameraCode={}, size={}", cameraCode, imageBytes.length);
|
||||
if (imageBytes == null || imageBytes.length == 0) {
|
||||
log.error("[AI截图] 下载图片为空: cameraCode={}, cosKey={}", cameraCode, cosKey);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 验证是否为有效的 JPEG 图片(检查魔数)
|
||||
if (imageBytes.length < 2 || (imageBytes[0] & 0xFF) != 0xFF || (imageBytes[1] & 0xFF) != 0xD8) {
|
||||
log.error("[AI截图] 下载的数据不是有效的JPEG: cameraCode={}, size={}", cameraCode, imageBytes.length);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 4. 更新 Redis 缓存(加速后续请求)
|
||||
writeCache(cameraCode, presignedUrl);
|
||||
log.debug("[AI截图] 代理图片成功: cameraCode={}, size={}", cameraCode, imageBytes.length);
|
||||
return imageBytes;
|
||||
} catch (Exception e) {
|
||||
log.error("[AI截图] 通过 DB cos_key 下载图片失败: cameraCode={}, cosKey={}, error={}",
|
||||
|
||||
Reference in New Issue
Block a user