From 647d19ab533a3402afc409f4151a72825bb6e5e9 Mon Sep 17 00:00:00 2001 From: WangGuangYuan Date: Mon, 26 Oct 2026 18:49:16 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E4=BF=9D=E5=AD=98=E9=A2=84=E7=BD=AE?= =?UTF-8?q?=E4=BD=8D=E7=BC=BA=E5=B0=91=20NVR=20IP=20=E6=97=B6=E6=8C=89=20c?= =?UTF-8?q?ameraCode=20=E5=8F=8D=E6=9F=A5=E6=89=80=E5=B1=9E=20NVR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/HikNvrUiActionController.java | 32 ++++++-- .../service/HikNvrCameraTargetService.java | 73 +++++++++++++++++-- 2 files changed, 91 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/inspect/nvr/hik/controller/HikNvrUiActionController.java b/src/main/java/com/inspect/nvr/hik/controller/HikNvrUiActionController.java index aef7fbbf..a732e837 100644 --- a/src/main/java/com/inspect/nvr/hik/controller/HikNvrUiActionController.java +++ b/src/main/java/com/inspect/nvr/hik/controller/HikNvrUiActionController.java @@ -2,12 +2,14 @@ package com.inspect.nvr.hik.controller; import com.inspect.nvr.dahua.DahuaIvsVendorService; import com.inspect.nvr.domain.Infrared.NvrInfo; +import com.inspect.nvr.hik.domain.HikNvrCameraTarget; import com.inspect.nvr.hik.domain.HikNvrLiveStreamResponse; import com.inspect.nvr.hik.domain.HikNvrPlaybackErrorResponse; import com.inspect.nvr.hik.domain.HikNvrPresetListResponse; import com.inspect.nvr.hik.domain.HikNvrUiActionRequest; import com.inspect.nvr.hik.domain.HikNvrUiActionResponse; import com.inspect.nvr.hik.exception.HikNvrPlaybackException; +import com.inspect.nvr.hik.service.HikNvrCameraTargetService; import com.inspect.nvr.hik.service.HikNvrCredentialService; import com.inspect.nvr.hik.service.HikNvrLivePublicBaseUrlResolver; import com.inspect.nvr.hik.service.HikNvrLiveStreamService; @@ -45,6 +47,9 @@ public class HikNvrUiActionController { /** 按页面选择的 NVR IP 从数据库读取服务端凭据。 */ @Resource private HikNvrCredentialService credentialService; + /** 按 IVS 完整 cameraCode 反查所属 NVR 与通道,供汇聚页面使用。 */ + @Resource + private HikNvrCameraTargetService cameraTargetService; /** 大华工作台预置位保存能力。 */ @Resource private DahuaIvsVendorService dahuaVendorService; @@ -182,7 +187,10 @@ public class HikNvrUiActionController { /** * 为页面请求绑定数据库中已启用的目标 NVR。 * - * @param request 页面操作请求,必须包含所选 NVR IP + *

页面显式提交 NVR IP 时按 IP 精确绑定;汇聚预览页只提交 cameraCode, + * 此时按 ivs_camera 与基于台账反查摄像机所属 NVR,禁止回退到默认设备。

+ * + * @param request 页面操作请求,必须包含 NVR IP 或 cameraCode * @return 绑定数据库目标 NVR 的海康操作服务 */ private HikNvrUiActionService actionServiceFor(HikNvrUiActionRequest request) { @@ -191,12 +199,24 @@ public class HikNvrUiActionController { /** 按页面请求读取并校验数据库中的目标 NVR。 */ private NvrInfo nvrInfoFor(HikNvrUiActionRequest request) { - // 多 NVR 环境必须明确目标 IP,禁止按固定配置或隐式默认设备执行控制。 - if (request == null || !hasText(request.getNvrIp())) { - throw HikNvrPlaybackException.badRequest("NVR IP不能为空"); + // 页面显式选择 NVR 时以页面选择为准,多 NVR 环境不允许隐式换设备。 + if (request != null && hasText(request.getNvrIp())) { + return credentialService.resolveByIp(request.getNvrIp().trim()); + } + // 汇聚页面不带 NVR IP,只能按摄像机编码确定其唯一所属 NVR。 + if (request != null && hasText(request.getCameraCode())) { + HikNvrCameraTarget target = cameraTargetService.resolve(request.getCameraCode()); + NvrInfo nvr = target.getNvrInfo(); + // 回填解析结果,后续海康与大华分支统一从请求读取 NVR IP。 + request.setNvrIp(nvr.getNvrIp()); + // 汇聚页面可能未传通道号,按 cameraCode 末尾通道补齐,避免落到默认通道。 + if (request.getChannel() == null || request.getChannel() <= 0) { + request.setChannel(target.getChannel()); + } + return nvr; } - // 只允许绑定数据库中已启用的 NVR,页面无法直接提供账号密码。 - return credentialService.resolveByIp(request.getNvrIp().trim()); + // 既没有 NVR IP 也没有 cameraCode 时无法安全定位数据库 NVR。 + throw HikNvrPlaybackException.badRequest("NVR IP不能为空"); } /** 判断数据库厂商字段是否表示大华。 */ diff --git a/src/main/java/com/inspect/nvr/hik/service/HikNvrCameraTargetService.java b/src/main/java/com/inspect/nvr/hik/service/HikNvrCameraTargetService.java index 7d7d4afc..a3b78bc8 100644 --- a/src/main/java/com/inspect/nvr/hik/service/HikNvrCameraTargetService.java +++ b/src/main/java/com/inspect/nvr/hik/service/HikNvrCameraTargetService.java @@ -49,6 +49,26 @@ public class HikNvrCameraTargetService { public HikNvrCameraTarget resolve(String cameraCode) { // 先校验完整编码结构,确保后续拆分不会把错误输入映射到其他设备。 String normalizedCode = normalizeCameraCode(cameraCode); + // 摄像机必须已同步到 ivs_camera,禁止仅凭编码格式访问未同步设备。 + requireSyncedCamera(normalizedCode); + // 设备编码最后三位是 IVS 页面逻辑通道号。 + int channel = parseChannel(normalizedCode); + // 去掉通道三位后恢复摄像机所属 NVR 的完整 IVS 编码。 + String expectedNvrCode = buildExpectedNvrCode(normalizedCode); + // 从现有 NVR 数据表中寻找唯一启用且编码完全匹配的设备。 + IvsNvr matchedNvr = findUniqueEnabledNvr(normalizedCode, expectedNvrCode); + // 按匹配出的数据库 IP 读取完整端口和凭据,供海康回放能力在服务端使用。 + NvrInfo nvrInfo = resolveNvrCredential(matchedNvr); + // 返回统一目标,调用方不再接受浏览器提交的 NVR IP 或通道号。 + return new HikNvrCameraTarget(normalizedCode, channel, nvrInfo); + } + + /** + * 校验规范化后的完整编码确实存在于 ivs_camera。 + * + * @param normalizedCode 已规范化的完整摄像机编码 + */ + private void requireSyncedCamera(String normalizedCode) { // 必须在 ivs_camera 中精确命中该编码,禁止仅凭编码格式访问未同步设备。 IvsCamera camera = ivsCameraMapper.selectByCode(normalizedCode); // 数据库没有该摄像机时返回明确的资源不存在错误。 @@ -56,12 +76,18 @@ public class HikNvrCameraTargetService { throw HikNvrPlaybackException.notFound( "ivs_camera中未找到cameraCode:" + normalizedCode); } + } - // 拆出 # 前设备编码和 # 后域编码,二者共同唯一定位所属 NVR。 + /** + * 从完整编码的设备段末尾解析 IVS 页面逻辑通道号。 + * + * @param normalizedCode 已规范化的完整摄像机编码 + * @return 大于 0 的页面逻辑通道号 + */ + private int parseChannel(String normalizedCode) { + // 拆出 # 前设备编码,末尾三位即通道号。 int separator = normalizedCode.indexOf('#'); String deviceCode = normalizedCode.substring(0, separator); - String domainCode = normalizedCode.substring(separator + 1); - // 设备编码最后三位是 IVS 页面逻辑通道号。 int channel = Integer.parseInt( deviceCode.substring(deviceCode.length() - CHANNEL_CODE_LENGTH)); // 000 不能作为可寻址通道,避免把无效摄像机编码交给海康 SDK。 @@ -69,11 +95,35 @@ public class HikNvrCameraTargetService { throw HikNvrPlaybackException.badRequest( "cameraCode未包含有效的三位通道号:" + normalizedCode); } + return channel; + } + + /** + * 由完整摄像机编码推导所属 NVR 的完整 IVS 编码。 + * + * @param normalizedCode 已规范化的完整摄像机编码 + * @return 设备编码去掉通道位后拼接域编码得到的 NVR 编码 + */ + private String buildExpectedNvrCode(String normalizedCode) { + // 拆出 # 前设备编码和 # 后域编码,二者共同唯一定位所属 NVR。 + int separator = normalizedCode.indexOf('#'); + String deviceCode = normalizedCode.substring(0, separator); + String domainCode = normalizedCode.substring(separator + 1); // 去掉通道三位后恢复摄像机所属 NVR 的完整 IVS 编码。 - String expectedNvrCode = deviceCode.substring( + return deviceCode.substring( 0, deviceCode.length() - CHANNEL_CODE_LENGTH) + "#" + domainCode; + } + /** + * 在启用 NVR 中查找编码与推导结果完全匹配且唯一的设备。 + * + * @param normalizedCode 已规范化的完整摄像机编码,用于错误提示 + * @param expectedNvrCode 由摄像机编码推导出的 NVR 完整编码 + * @return 唯一匹配的启用 NVR 记录 + */ + private IvsNvr findUniqueEnabledNvr( + String normalizedCode, String expectedNvrCode) { // 从现有 NVR 数据表中寻找唯一启用且编码完全匹配的设备。 List nvrs = ivsNvrMapper.selectAll(); IvsNvr matchedNvr = null; @@ -102,12 +152,19 @@ public class HikNvrCameraTargetService { throw HikNvrPlaybackException.notFound( "未找到cameraCode对应的启用NVR:" + normalizedCode); } + return matchedNvr; + } - // 按匹配出的数据库 IP 读取完整端口和凭据,供海康回放能力在服务端使用。 - NvrInfo nvrInfo = credentialService.resolveByIp( + /** + * 按匹配 NVR 的数据库 IP 读取回放所需的端口与凭据。 + * + * @param matchedNvr 已唯一匹配的启用 NVR 记录 + * @return 包含端口和凭据的 NVR 连接信息 + */ + private NvrInfo resolveNvrCredential(IvsNvr matchedNvr) { + // IP 已在 isEnabledTarget 中确认非空,这里统一去除首尾空白。 + return credentialService.resolveByIp( matchedNvr.getNvrIp().trim()); - // 返回统一目标,调用方不再接受浏览器提交的 NVR IP 或通道号。 - return new HikNvrCameraTarget(normalizedCode, channel, nvrInfo); } /**