diff --git a/docs/sql/ivs_camera.sql b/docs/sql/ivs_camera.sql index e5445bc3..7faa7457 100644 --- a/docs/sql/ivs_camera.sql +++ b/docs/sql/ivs_camera.sql @@ -26,7 +26,7 @@ CREATE TABLE IF NOT EXISTS `ivs_camera` ( `reserve` varchar(500) DEFAULT NULL COMMENT '预留字段', `custom_fields` text COMMENT '自定义字段', `function_type` varchar(255) DEFAULT NULL COMMENT '功能类型', - `ptz_supported` tinyint DEFAULT NULL COMMENT '云台能力:1支持,0不支持,NULL未探测', + `ptz_supported` tinyint DEFAULT NULL COMMENT '云台能力:1支持,0不支持,NULL未知', `monitor_direction` varchar(32) DEFAULT NULL COMMENT '监控方向', `owner_aps_id` varchar(128) DEFAULT NULL COMMENT '所属 APS 编码', `security_level_code_string` varchar(32) DEFAULT NULL COMMENT '安全等级编码', diff --git a/src/main/java/com/inspect/nvr/hik/domain/HikNvrPlaybackUiConfig.java b/src/main/java/com/inspect/nvr/hik/domain/HikNvrPlaybackUiConfig.java index 44b23bd1..1abf3ea7 100644 --- a/src/main/java/com/inspect/nvr/hik/domain/HikNvrPlaybackUiConfig.java +++ b/src/main/java/com/inspect/nvr/hik/domain/HikNvrPlaybackUiConfig.java @@ -94,13 +94,7 @@ public class HikNvrPlaybackUiConfig { /** 设备分组名称。 */ private String group; - /** - * 该通道是否支持云台控制,供前端对不可控通道置灰云台面板。 - * - *
按台账 basedata_eqpbook_channel.channel_type 静态判定,与设备树节点上的 - * 同名字段使用同一套规则:与镜头无关的 ptz、ng 通道不支持,台账查不到信息的 - * 通道保守放行。
- */ + /** 该通道是否支持云台控制,供前端对不可控通道置灰云台面板。 */ private Boolean ptzSupported; } } diff --git a/src/main/java/com/inspect/nvr/hik/service/HikNvrDeviceTreeService.java b/src/main/java/com/inspect/nvr/hik/service/HikNvrDeviceTreeService.java index 1887fde8..e087cbf0 100644 --- a/src/main/java/com/inspect/nvr/hik/service/HikNvrDeviceTreeService.java +++ b/src/main/java/com/inspect/nvr/hik/service/HikNvrDeviceTreeService.java @@ -171,9 +171,8 @@ public class HikNvrDeviceTreeService { /** * 把 ivs_camera 中落库的云台能力转换为页面布尔值。 * - *能力值由同步任务向 NVR 探测得到:1 支持、0 不支持、NULL 未探测。未探测表示 - * 尚无结论(通道离线、探测失败、非海康厂商等),必须按"支持"返回,否则一次探测 - * 失败就会把云台面板误置灰。
+ *能力值由人工维护:1 支持、0 不支持、NULL 未知。未知必须按"支持"返回, + * 否则尚未维护的通道会被整片误置灰。
* * @param capability ivs_camera.ptz_supported 落库值 * @return 支持云台控制返回 true diff --git a/src/main/java/com/inspect/nvr/ivs/pojo/IvsCamera.java b/src/main/java/com/inspect/nvr/ivs/pojo/IvsCamera.java index a86dae49..c20e566e 100644 --- a/src/main/java/com/inspect/nvr/ivs/pojo/IvsCamera.java +++ b/src/main/java/com/inspect/nvr/ivs/pojo/IvsCamera.java @@ -68,7 +68,7 @@ public class IvsCamera implements Serializable { private String customFields; /** 功能类型。 */ private String functionType; - /** 云台能力:1 支持,0 不支持,NULL 未探测(通道离线或探测失败)。 */ + /** 云台能力:1 支持,0 不支持,NULL 未知;人工维护,同步任务不回写该列。 */ private Integer ptzSupported; /** 监控方向。 */ private String monitorDirection; diff --git a/src/main/java/com/inspect/nvr/ivs/service/IvsCameraSyncService.java b/src/main/java/com/inspect/nvr/ivs/service/IvsCameraSyncService.java index 7da0011a..9b288694 100644 --- a/src/main/java/com/inspect/nvr/ivs/service/IvsCameraSyncService.java +++ b/src/main/java/com/inspect/nvr/ivs/service/IvsCameraSyncService.java @@ -15,10 +15,8 @@ import org.springframework.scheduling.annotation.Scheduled; import javax.annotation.Resource; import java.security.SecureRandom; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.UUID; /** @@ -91,20 +89,10 @@ public class IvsCameraSyncService { ivsNvrMapper.updateChannelCount(current.getNvrId(), channels.size()); current.setChannelCount(channels.size()); } - // 读取本台 NVR 已落库的云台能力结论,避免每轮同步重复探测设备。 - Map同步任务每五分钟执行一次,已有结论的通道不再重复探测,否则每轮都会对每个 - * 通道发一次 ISAPI 请求。通道离线、探测失败或需要重新探测(手工把 - * {@code ivs_camera.ptz_supported} 置空)时才会再次探测。
- * - * @param nvr 当前同步的 NVR - * @return 摄像机编码到已落库云台能力的映射,未落库或查询无结果时对应编码不出现 - */ - private Map云台能力探测需要区分"设备明确拒绝"与"接口本身不可用",因此不能像 - * {@link #request} 那样把非 2xx 一律当异常抛出。
- * - * @param nvr NVR 连接与凭据 - * @param path ISAPI 路径(含前导斜杠) - * @return 状态码与解码后的响应体 - */ - private IsapiResponse exchange(IvsNvr nvr, String path) { // 请求必须提供 NVR IP。 if (nvr == null || isBlank(nvr.getNvrIp())) { throw new IllegalArgumentException("NVR IP 不能为空"); @@ -172,8 +144,13 @@ public class IvsNvrChannelClient { // 读取响应体原始字节,避免 HTTP 客户端使用平台默认字符集破坏中文名称。 byte[] bytes = response.getEntity() == null ? new byte[0] : EntityUtils.toByteArray(response.getEntity()); - // 将设备响应解码成 XML 文本;非 2xx 的响应体同样解码,交由调用方判定。 - return new IsapiResponse(status, decode(bytes)); + // 非 2xx 状态表示 NVR 通道接口调用失败。 + if (status < 200 || status >= 300) { + throw new IllegalStateException("NVR ISAPI 返回 HTTP " + status + + ",IP=" + nvr.getNvrIp()); + } + // 将设备响应解码成 XML 文本。 + return decode(bytes); }); } catch (Exception ex) { // 保留已知业务异常,其他异常包装为通道读取失败。 @@ -185,91 +162,6 @@ public class IvsNvrChannelClient { } } - /** - * 向 NVR 探测指定通道是否支持云台控制。 - * - *海康 ISAPI 的云台能力接口只对可云台通道返回设备能力结构;固定摄像机(枪机、 - * 半球等)由设备明确拒绝。因此判定分三种结果,其中"未探测"绝不能当成"不支持", - * 否则接口本身不可用或凭据异常时会把整台 NVR 的通道全部置灰。
- * - * @param nvr NVR 连接与凭据 - * @param channelId NVR 通道号,空值或无意义通道号直接返回未探测 - * @return 1 支持,0 不支持,null 未探测(接口不可用、鉴权失败或响应无法判定) - */ - public Integer probePtzSupported(IvsNvr nvr, Integer channelId) { - // 云台能力接口由海康 ISAPI 提供,其他厂商保持"未探测",不发无效请求。 - if (nvr == null || !"HIKVISION".equals(normalizeVendor(nvr.getVendor()))) { - log.info("该厂商暂不支持云台能力探测,nvrIp={},vendor={}", - nvr == null ? null : nvr.getNvrIp(), - nvr == null ? null : nvr.getVendor()); - return null; - } - // 通道号缺失时无法拼出能力查询地址,保持"未探测"而不是让同步任务失败。 - if (channelId == null || channelId <= 0) { - log.info("云台能力探测缺少有效通道号,保持未探测,nvrIp={},channel={}", - nvr.getNvrIp(), channelId); - return null; - } - IsapiResponse response; - try { - response = exchange(nvr, String.format(PTZ_CAPABILITIES_PATH, channelId)); - } catch (Exception ex) { - // 网络异常或参数异常只记录原因,保持"未探测",下一轮同步重试。 - log.error("云台能力探测失败,nvrIp={},channel={}:{}", - nvr.getNvrIp(), channelId, ex.getMessage()); - return null; - } - String body = response.body == null ? "" : response.body; - // 能力接口返回设备能力结构即代表该通道可云台控制。 - if ((response.status >= 200 && response.status < 300) - || body.contains(PTZ_CAPABILITY_TAG)) { - log.info("云台能力探测:支持云台,nvrIp={},channel={}", - nvr.getNvrIp(), channelId); - return 1; - } - // 设备用标准 ISAPI 错误体明确拒绝时判为不支持;鉴权失败必须排除在外。 - if (response.status != 401 && !body.contains(UNAUTHORIZED_TAG) - && body.contains(ISAPI_ERROR_TAG)) { - log.info("云台能力探测:不支持云台,nvrIp={},channel={},http={},statusString={}", - nvr.getNvrIp(), channelId, response.status, extractStatusString(body)); - return 0; - } - // 其余情况(接口不存在、网关错误、空响应等)无法判定,保持"未探测"。 - log.info("云台能力探测结果无法判定,保持未探测,nvrIp={},channel={},http={},body={}", - nvr.getNvrIp(), channelId, response.status, abbreviate(body)); - return null; - } - - /** 提取 ISAPI 错误体中的 statusString,便于日志直接看出设备拒绝原因。 */ - private String extractStatusString(String body) { - int start = body.indexOf("