Revert "feat(aiot): 截图持久化 + ROI 显示修复 + 告警图片代理"
This reverts commit 547dfdd5f4.
This commit is contained in:
@@ -9,8 +9,6 @@ import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
@@ -62,19 +60,6 @@ public class AiAlertController {
|
||||
return alertService.statistics(startTime);
|
||||
}
|
||||
|
||||
@Operation(summary = "告警图片代理")
|
||||
@GetMapping("/image")
|
||||
public ResponseEntity<byte[]> getAlertImage(@RequestParam String imagePath) {
|
||||
byte[] image = alertService.proxyAlertImage(imagePath);
|
||||
if (image == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_JPEG)
|
||||
.header("Cache-Control", "public, max-age=3600")
|
||||
.body(image);
|
||||
}
|
||||
|
||||
// ==================== Edge 告警上报 ====================
|
||||
|
||||
@Operation(summary = "Edge 告警上报")
|
||||
|
||||
@@ -124,7 +124,7 @@ public class AiRoiController {
|
||||
}
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_JPEG)
|
||||
.header("Cache-Control", "public, max-age=300")
|
||||
.header("Cache-Control", "max-age=60")
|
||||
.body(image);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.genersoft.iot.vmp.aiot.dao;
|
||||
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
@Mapper
|
||||
public interface AiCameraSnapshotMapper {
|
||||
|
||||
@Select("SELECT cos_key FROM wvp_ai_camera_snapshot WHERE camera_code=#{cameraCode}")
|
||||
String getCosKey(@Param("cameraCode") String cameraCode);
|
||||
|
||||
@Insert("INSERT INTO wvp_ai_camera_snapshot (camera_code, cos_key) " +
|
||||
"VALUES (#{cameraCode}, #{cosKey}) " +
|
||||
"ON DUPLICATE KEY UPDATE cos_key=#{cosKey}, updated_at=NOW()")
|
||||
int upsert(@Param("cameraCode") String cameraCode, @Param("cosKey") String cosKey);
|
||||
}
|
||||
@@ -21,6 +21,4 @@ public interface IAiAlertService {
|
||||
Map<String, Object> statistics(String startTime);
|
||||
|
||||
void updateDuration(String alertId, double durationMinutes);
|
||||
|
||||
byte[] proxyAlertImage(String imagePath);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.genersoft.iot.vmp.aiot.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.aiot.bean.AiAlert;
|
||||
import com.genersoft.iot.vmp.aiot.dao.AiAlertMapper;
|
||||
import com.genersoft.iot.vmp.aiot.service.IAiAlertService;
|
||||
@@ -9,13 +7,8 @@ import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
@@ -27,9 +20,6 @@ public class AiAlertServiceImpl implements IAiAlertService {
|
||||
@Autowired
|
||||
private AiAlertMapper alertMapper;
|
||||
|
||||
@Value("${ai.alert.presign-url:http://127.0.0.1:8000/admin-api/aiot/storage/presign}")
|
||||
private String presignUrl;
|
||||
|
||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@Override
|
||||
@@ -82,20 +72,4 @@ public class AiAlertServiceImpl implements IAiAlertService {
|
||||
public void updateDuration(String alertId, double durationMinutes) {
|
||||
alertMapper.updateDuration(alertId, durationMinutes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] proxyAlertImage(String imagePath) {
|
||||
try {
|
||||
RestTemplate rest = new RestTemplate();
|
||||
String endpoint = presignUrl + "?objectKey=" +
|
||||
URLEncoder.encode(imagePath, StandardCharsets.UTF_8);
|
||||
String json = rest.getForObject(endpoint, String.class);
|
||||
JSONObject resp = JSON.parseObject(json);
|
||||
String cosUrl = resp.getJSONObject("data").getString("url");
|
||||
return rest.getForObject(URI.create(cosUrl), byte[].class);
|
||||
} catch (Exception e) {
|
||||
log.error("[AiAlert] 代理告警图片失败: path={}, error={}", imagePath, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.aiot.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.aiot.dao.AiCameraSnapshotMapper;
|
||||
import com.genersoft.iot.vmp.aiot.service.IAiScreenshotService;
|
||||
import com.genersoft.iot.vmp.streamProxy.bean.StreamProxy;
|
||||
import com.genersoft.iot.vmp.streamProxy.dao.StreamProxyMapper;
|
||||
@@ -17,8 +16,6 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
@@ -44,24 +41,15 @@ public class AiScreenshotServiceImpl implements IAiScreenshotService {
|
||||
/** 等待 Edge 回调的 pending 请求表 */
|
||||
private final ConcurrentHashMap<String, CompletableFuture<Map<String, Object>>> pendingRequests = new ConcurrentHashMap<>();
|
||||
|
||||
/** requestId → cosPath 映射,用于回调时持久化 cos_key */
|
||||
private final ConcurrentHashMap<String, String> pendingCosKeys = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Autowired
|
||||
private StreamProxyMapper streamProxyMapper;
|
||||
|
||||
@Autowired
|
||||
private AiCameraSnapshotMapper snapshotMapper;
|
||||
|
||||
@Value("${ai.screenshot.callback-url:}")
|
||||
private String callbackUrl;
|
||||
|
||||
@Value("${ai.alert.presign-url:http://127.0.0.1:8000/admin-api/aiot/storage/presign}")
|
||||
private String presignUrl;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> requestScreenshot(String cameraCode, boolean force) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
@@ -112,7 +100,6 @@ public class AiScreenshotServiceImpl implements IAiScreenshotService {
|
||||
try {
|
||||
MapRecord<String, String, String> record = MapRecord.create(SNAP_REQUEST_STREAM, fields);
|
||||
RecordId recordId = stringRedisTemplate.opsForStream().add(record);
|
||||
pendingCosKeys.put(requestId, cosPath);
|
||||
log.info("[AI截图] 发送截图请求: requestId={}, cameraCode={}, streamId={}", requestId, cameraCode, recordId);
|
||||
} catch (Exception e) {
|
||||
log.error("[AI截图] 发送截图请求失败: {}", e.getMessage());
|
||||
@@ -179,7 +166,6 @@ public class AiScreenshotServiceImpl implements IAiScreenshotService {
|
||||
return result;
|
||||
} finally {
|
||||
pendingRequests.remove(requestId);
|
||||
pendingCosKeys.remove(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,19 +182,6 @@ public class AiScreenshotServiceImpl implements IAiScreenshotService {
|
||||
if ("ok".equals(status) && cameraCode != null) {
|
||||
String url = (String) data.get("url");
|
||||
writeCache(cameraCode, url);
|
||||
|
||||
// 持久化 cos_key 到数据库
|
||||
String cosKey = pendingCosKeys.remove(requestId);
|
||||
if (cosKey != null) {
|
||||
try {
|
||||
snapshotMapper.upsert(cameraCode, cosKey);
|
||||
log.info("[AI截图] cos_key 已持久化: cameraCode={}, cosKey={}", cameraCode, cosKey);
|
||||
} catch (Exception e) {
|
||||
log.warn("[AI截图] cos_key 持久化失败: cameraCode={}, error={}", cameraCode, e.getMessage());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pendingCosKeys.remove(requestId);
|
||||
}
|
||||
|
||||
CompletableFuture<Map<String, Object>> future = pendingRequests.get(requestId);
|
||||
@@ -245,49 +218,27 @@ public class AiScreenshotServiceImpl implements IAiScreenshotService {
|
||||
|
||||
@Override
|
||||
public byte[] proxyScreenshotImage(String cameraCode) {
|
||||
// 1. 先查 Redis 缓存(5分钟内的 presigned URL)
|
||||
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 rest = new RestTemplate();
|
||||
byte[] bytes = rest.getForObject(URI.create(cosUrl), byte[].class);
|
||||
if (bytes != null) return bytes;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[AI截图] Redis 缓存 URL 下载失败,尝试 DB: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 查 DB 持久化的 cos_key(永不过期)
|
||||
String cosKey = snapshotMapper.getCosKey(cameraCode);
|
||||
if (cosKey == null) {
|
||||
log.warn("[AI截图] 代理图片: 无缓存也无持久化记录 cameraCode={}", cameraCode);
|
||||
if (cacheJson == null) {
|
||||
log.warn("[AI截图] 代理图片: 缓存不存在 cameraCode={}", cameraCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 调 FastAPI 生成新的 presigned URL
|
||||
try {
|
||||
RestTemplate rest = new RestTemplate();
|
||||
String endpoint = presignUrl + "?objectKey=" +
|
||||
URLEncoder.encode(cosKey, StandardCharsets.UTF_8);
|
||||
String json = rest.getForObject(endpoint, String.class);
|
||||
JSONObject resp = JSON.parseObject(json);
|
||||
String cosUrl = resp.getJSONObject("data").getString("url");
|
||||
|
||||
// 4. 下载并返回图片
|
||||
byte[] imageBytes = rest.getForObject(URI.create(cosUrl), byte[].class);
|
||||
|
||||
// 5. 更新 Redis 缓存(加速后续请求)
|
||||
if (imageBytes != null) {
|
||||
writeCache(cameraCode, cosUrl);
|
||||
JSONObject cached = JSON.parseObject(cacheJson);
|
||||
String cosUrl = cached.getString("url");
|
||||
if (cosUrl == null || cosUrl.isEmpty()) {
|
||||
log.warn("[AI截图] 代理图片: 缓存中无 URL cameraCode={}", cameraCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 使用 URI.create 避免 RestTemplate 对已编码的预签名 URL 做二次编码
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
byte[] imageBytes = restTemplate.getForObject(URI.create(cosUrl), byte[].class);
|
||||
log.debug("[AI截图] 代理图片成功: cameraCode={}, size={}", cameraCode, imageBytes != null ? imageBytes.length : 0);
|
||||
return imageBytes;
|
||||
} catch (Exception e) {
|
||||
log.error("[AI截图] 通过 DB cos_key 下载图片失败: cameraCode={}, cosKey={}, error={}",
|
||||
cameraCode, cosKey, e.getMessage());
|
||||
log.error("[AI截图] 代理下载图片失败: cameraCode={}, error={}", cameraCode, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@ public class WebSecurityConfig {
|
||||
defaultExcludes.add("/api/ai/roi/snap/image");
|
||||
defaultExcludes.add("/api/ai/camera/get");
|
||||
defaultExcludes.add("/api/ai/alert/edge/**");
|
||||
defaultExcludes.add("/api/ai/alert/image");
|
||||
defaultExcludes.add("/api/ai/device/edge/**");
|
||||
|
||||
if (userSetting.getInterfaceAuthentication() && !userSetting.getInterfaceAuthenticationExcludes().isEmpty()) {
|
||||
|
||||
Reference in New Issue
Block a user