From 0d1e0da84c1d46ab3d9a4c80e7c3b066f9bacb3d Mon Sep 17 00:00:00 2001 From: wangguangyuan Date: Mon, 28 Sep 2026 18:23:24 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=E9=80=9A=E8=BF=87=20NVR=20SDK=20?= =?UTF-8?q?=E6=8A=93=E5=9B=BE=EF=BC=8C=E5=88=9B=E5=BB=BA=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=9D=83=E9=99=90=E4=B8=8D=E8=B6=B3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 +- docker-compose.yml | 4 ++ .../hik/service/HikNvrLiveStreamService.java | 4 +- .../nvr/service/CommonCameraService.java | 56 +++++++++++++++---- 4 files changed, 53 insertions(+), 15 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5ca4aa2e..ec4e5f01 100644 --- a/Dockerfile +++ b/Dockerfile @@ -90,8 +90,8 @@ COPY --from=ffmpeg /ffprobe /usr/bin/ffprobe RUN groupadd --gid 10001 inspect-nvr \ && useradd --uid 10001 --gid inspect-nvr --home-dir /app --shell /usr/sbin/nologin inspect-nvr \ && mkdir -p /app/lib/HCNetSDKCom /app/lib/linux64 \ - /app/images /app/sdkLog /app/sdklog /app/target /data/logs/inspect-nvr \ - && chown -R inspect-nvr:inspect-nvr /app /data/logs + /app/images /app/sdkLog /app/sdklog /app/target /data/captures /data/logs/inspect-nvr \ + && chown -R inspect-nvr:inspect-nvr /app /data/captures /data/logs WORKDIR /app diff --git a/docker-compose.yml b/docker-compose.yml index e9e60dd9..7ceffeac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,8 @@ services: TZ: ${TZ:-Asia/Shanghai} SPRING_PROFILES_ACTIVE: docker SERVER_PORT: 8081 + # 抓图写入独立持久卷,避免应用目录权限或容器重建导致抓图失败/丢失。 + INSPECT_NVR_CAPTURE_DIR: /data/captures DB_URL: ${DB_URL:?DB_URL_required_in_.env} DB_USERNAME: ${DB_USERNAME:?DB_USERNAME_required_in_.env} DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD_required_in_.env} @@ -53,6 +55,7 @@ services: - app_dahua_sdk_logs:/app/sdklog - app_images:/app/images - app_runtime:/app/target + - app_captures:/data/captures networks: - inspect-nvr @@ -66,3 +69,4 @@ volumes: app_dahua_sdk_logs: app_images: app_runtime: + app_captures: diff --git a/src/main/java/com/inspect/nvr/hik/service/HikNvrLiveStreamService.java b/src/main/java/com/inspect/nvr/hik/service/HikNvrLiveStreamService.java index 22da5639..4ee3e5e5 100644 --- a/src/main/java/com/inspect/nvr/hik/service/HikNvrLiveStreamService.java +++ b/src/main/java/com/inspect/nvr/hik/service/HikNvrLiveStreamService.java @@ -1872,12 +1872,12 @@ public class HikNvrLiveStreamService { /** 返回当前流的 HLS 浏览器地址。 */ private String hlsUrl(String streamId, String baseUrl) { - return baseUrl + "/live/" + streamId + "/hls.m3u8"; + return "/live/" + streamId + "/hls.m3u8"; } /** 返回当前流的 HTTP-FLV 浏览器地址。 */ private String flvUrl(String streamId, String baseUrl) { - return baseUrl + "/live/" + streamId + ".live.flv"; + return "/live/" + streamId + ".live.flv"; } /** diff --git a/src/main/java/com/inspect/nvr/service/CommonCameraService.java b/src/main/java/com/inspect/nvr/service/CommonCameraService.java index 5b8986f3..ecac0127 100644 --- a/src/main/java/com/inspect/nvr/service/CommonCameraService.java +++ b/src/main/java/com/inspect/nvr/service/CommonCameraService.java @@ -15,6 +15,7 @@ import org.springframework.util.StreamUtils; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -33,7 +34,7 @@ import java.util.concurrent.locks.ReentrantLock; public class CommonCameraService { /** 摄像机抓图失败时写入的兜底图片路径。 */ static final String IMAGE_CAPTURE_FAILED = "images\\imageCaptureFailed.jpg"; - /** 抓图保存根目录,由当前操作系统和运行目录决定。 */ + /** 抓图保存根目录,可由 INSPECT_NVR_CAPTURE_DIR 指定。 */ public static final String FILE_DIR = determineCaptureDirectory(); /** 厂商 SDK 抓图失败时的默认最大重试次数。 */ static final int DEFAULT_MAX_RETRIES = 10; @@ -201,8 +202,10 @@ public class CommonCameraService { Path getFullPath(String flag, String ip, int channel) { String timeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss_SSS")); String fileName = flag + "_" + ip + "_" + channel + "_" + timeStr + ".jpg"; - String saveDir = FILE_DIR + LocalDate.now(); - Path fullPath = Paths.get(saveDir, fileName).toAbsolutePath(); + Path fullPath = Paths.get(FILE_DIR) + .resolve(LocalDate.now().toString()) + .resolve(fileName) + .toAbsolutePath(); ensureDirectoryExists(fullPath.getParent()); return fullPath; } @@ -212,16 +215,47 @@ public class CommonCameraService { * 注意路径避免包含中文,否则可能会导致文件保存失败或乱码 */ private static String determineCaptureDirectory() { - String os = System.getProperty("os.name").toLowerCase(); - log.info("当前操作系统: {}", os); - String userDir = System.getProperty("user.dir"); - log.info("userDir: {}", userDir); + String configuredDirectory = System.getenv("INSPECT_NVR_CAPTURE_DIR"); + if (configuredDirectory == null || configuredDirectory.trim().isEmpty()) { + configuredDirectory = System.getProperty("inspect.nvr.capture-dir"); + } - if (os.contains("win")) { - return "D:/captures/"; + Path preferredDirectory; + if (configuredDirectory != null && !configuredDirectory.trim().isEmpty()) { + preferredDirectory = Paths.get(configuredDirectory.trim()).toAbsolutePath(); + } else if (System.getProperty("os.name", "").toLowerCase().contains("win")) { + preferredDirectory = Paths.get("D:/captures").toAbsolutePath(); } else { - // Linux/Unix/Mac 系统使用项目同级目录 - return userDir + "/captures/"; + preferredDirectory = Paths.get(System.getProperty("user.dir"), "captures").toAbsolutePath(); + } + + try { + ensureWritableDirectory(preferredDirectory); + log.info("抓图目录已就绪: {}", preferredDirectory); + return preferredDirectory.toString() + java.io.File.separator; + } catch (IOException primaryFailure) { + Path fallbackDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "inspect-nvr-captures") + .toAbsolutePath(); + try { + ensureWritableDirectory(fallbackDirectory); + log.warn("抓图目录 {} 不可写,已回退到 {}:{}", + preferredDirectory, fallbackDirectory, primaryFailure.toString()); + return fallbackDirectory.toString() + java.io.File.separator; + } catch (IOException fallbackFailure) { + fallbackFailure.addSuppressed(primaryFailure); + throw new IllegalStateException("抓图目录不可写,主目录=" + preferredDirectory + + ",临时目录=" + fallbackDirectory, fallbackFailure); + } + } + } + + /** 创建目录并通过真实文件写入确认运行用户拥有写权限。 */ + private static void ensureWritableDirectory(Path directory) throws IOException { + Files.createDirectories(directory); + if (!Files.isDirectory(directory)) { + throw new FileAlreadyExistsException(directory.toString(), null, "目标路径不是目录"); } + Path probe = Files.createTempFile(directory, ".inspect-nvr-write-check-", ".tmp"); + Files.delete(probe); } }