diff --git a/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiRoiController.java b/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiRoiController.java index 1703edb34..43cc48997 100644 --- a/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiRoiController.java +++ b/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiRoiController.java @@ -10,19 +10,18 @@ import com.github.pagehelper.PageInfo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.compress.utils.IOUtils; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; import java.util.List; +import java.util.concurrent.TimeUnit; @Slf4j @RestController @@ -92,40 +91,56 @@ public class AiRoiController { @Operation(summary = "获取摄像头截图") @GetMapping("/snap") public void getSnap(HttpServletResponse resp, - @RequestParam String app, - @RequestParam String stream) { + @RequestParam String url) { MediaServer mediaServer = mediaServerService.getDefaultMediaServer(); if (mediaServer == null) { + log.warn("[AI截图] 无可用媒体服务器"); resp.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); return; } - String snapDir = "snap" + File.separator + "ai"; - String fileName = app + "_" + stream + ".jpg"; - // 调用ZLM截图 - mediaServerService.getSnap(mediaServer, app, stream, 10, 1, snapDir, fileName); - // 等待截图生成 - File snapFile = new File(snapDir + File.separator + fileName); - int retries = 0; - while (!snapFile.exists() && retries < 50) { - try { - Thread.sleep(200); - } catch (InterruptedException ignored) {} - retries++; - } - if (!snapFile.exists()) { - resp.setStatus(HttpServletResponse.SC_NO_CONTENT); + // 直接调用ZLM的getSnap同步接口,传入RTSP源地址 + String zlmApi = String.format("http://%s:%s/index/api/getSnap", + mediaServer.getIp(), mediaServer.getHttpPort()); + HttpUrl httpUrl = HttpUrl.parse(zlmApi); + if (httpUrl == null) { + resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } + HttpUrl requestUrl = httpUrl.newBuilder() + .addQueryParameter("secret", mediaServer.getSecret()) + .addQueryParameter("url", url) + .addQueryParameter("timeout_sec", "15") + .addQueryParameter("expire_sec", "1") + .build(); + + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(5, TimeUnit.SECONDS) + .readTimeout(20, TimeUnit.SECONDS) + .build(); + try { - InputStream in = Files.newInputStream(snapFile.toPath()); - resp.setContentType(MediaType.IMAGE_JPEG_VALUE); - ServletOutputStream outputStream = resp.getOutputStream(); - IOUtils.copy(in, outputStream); - in.close(); - outputStream.close(); - } catch (IOException e) { - log.warn("[AI截图] 读取截图失败: {}", e.getMessage()); - resp.setStatus(HttpServletResponse.SC_NO_CONTENT); + Request request = new Request.Builder().url(requestUrl).build(); + Response response = client.newCall(request).execute(); + if (response.isSuccessful() && response.body() != null) { + String contentType = response.header("Content-Type", ""); + if (contentType.contains("image")) { + resp.setContentType(MediaType.IMAGE_JPEG_VALUE); + byte[] bytes = response.body().bytes(); + resp.getOutputStream().write(bytes); + resp.getOutputStream().flush(); + } else { + // ZLM返回的是JSON错误信息,不是图片 + log.warn("[AI截图] ZLM未返回图片,可能流未在线: {}", response.body().string()); + resp.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + } else { + log.warn("[AI截图] ZLM请求失败: {} {}", response.code(), response.message()); + resp.setStatus(HttpServletResponse.SC_BAD_GATEWAY); + } + response.close(); + } catch (Exception e) { + log.warn("[AI截图] 截图异常: {}", e.getMessage()); + resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } } diff --git a/web/src/api/cameraConfig.js b/web/src/api/cameraConfig.js index c5a84bff2..cf1021304 100644 --- a/web/src/api/cameraConfig.js +++ b/web/src/api/cameraConfig.js @@ -25,6 +25,6 @@ export function stopCamera(id) { }) } -export function getSnapUrl(cameraId) { - return `/api/ai/roi/snap?cameraId=${encodeURIComponent(cameraId)}` +export function getSnapUrl(srcUrl) { + return `/api/ai/roi/snap?url=${encodeURIComponent(srcUrl)}` } diff --git a/web/src/views/cameraConfig/roiConfig.vue b/web/src/views/cameraConfig/roiConfig.vue index d67415239..fea401b3c 100644 --- a/web/src/views/cameraConfig/roiConfig.vue +++ b/web/src/views/cameraConfig/roiConfig.vue @@ -113,10 +113,8 @@ export default { mounted() { this.cameraId = decodeURIComponent(this.$route.params.cameraId) this.srcUrl = this.$route.query.srcUrl || '' - const app = this.$route.query.app || '' - const stream = this.$route.query.stream || '' - if (app && stream) { - this.snapUrl = `/api/ai/roi/snap?app=${encodeURIComponent(app)}&stream=${encodeURIComponent(stream)}` + if (this.srcUrl) { + this.snapUrl = `/api/ai/roi/snap?url=${encodeURIComponent(this.srcUrl)}` } this.loadRois() }, @@ -147,10 +145,8 @@ export default { this.drawMode = mode }, refreshSnap() { - const app = this.$route.query.app || '' - const stream = this.$route.query.stream || '' - if (app && stream) { - this.snapUrl = `/api/ai/roi/snap?app=${encodeURIComponent(app)}&stream=${encodeURIComponent(stream)}&t=${Date.now()}` + if (this.srcUrl) { + this.snapUrl = `/api/ai/roi/snap?url=${encodeURIComponent(this.srcUrl)}&t=${Date.now()}` } }, onRoiDrawn(data) {