# 海康 NVR 历史录像查询与 RTSP 回放接口设计 ## 目标 在 `com.inspect.nvr.hik` 下新增海康 NVR 历史录像查询接口。调用方传入 NVR 登录信息、通道、码流类型以及北京时间范围,服务端通过海康网络 SDK 查询该范围内实际存在的录像,返回实际连续录像区间及每个区间对应的 RTSP 回放地址。 本功能只查询录像并生成回放地址,不代理、转码或下载视频流。 ## HTTP 接口 - 方法:`POST` - 路径:`/hik/nvr/playback/search` - Content-Type:`application/json` - Swagger 分组:`海康NVR回放` ### 请求体 ```json { "nvrIp": "192.168.1.100", "sdkPort": 8000, "rtspPort": 554, "channel": 1, "streamType": 1, "username": "admin", "password": "123456", "startTime": "2026-07-13 12:00:00", "endTime": "2026-07-13 13:00:00" } ``` 字段规则: - `nvrIp`、`username`、`password` 必填。 - `sdkPort` 范围为 1~65535,用于 SDK 登录,通常为 8000。 - `rtspPort` 范围为 1~65535,通常为 554。 - `channel` 必须大于 0,使用海康 NVR 实际 SDK 通道号。 - `streamType` 取值 1 或 2;1 表示主码流,2 表示子码流。 - `startTime`、`endTime` 格式固定为 `yyyy-MM-dd HH:mm:ss`,语义固定为 `Asia/Shanghai`。 - `endTime` 必须晚于 `startTime`。 ### 成功响应 有录像: ```json { "code": 0, "message": "success", "hasRecord": true, "records": [ { "timeRange": [ "20260713123000", "20260713130000" ], "rtspUrl": "rtsp://admin:123456@192.168.1.100:554/Streaming/tracks/101?starttime=20260713T043000Z&endtime=20260713T050000Z" } ] } ``` 无录像: ```json { "code": 0, "message": "success", "hasRecord": false, "records": [] } ``` `timeRange` 始终包含两个北京时间字符串,顺序为 `[开始时间, 结束时间]`,格式固定为 `yyyyMMddHHmmss`。 ## 组件划分 代码全部位于 `src/main/java/com/inspect/nvr/hik`: - `controller/HikNvrPlaybackController`:接收 HTTP 请求、调用服务并返回响应。 - `domain/HikNvrPlaybackRequest`:请求模型和 Swagger 字段说明;密码标记为只写字段。 - `domain/HikNvrPlaybackResponse`:查询状态、是否存在录像和录像区间列表。 - `domain/HikNvrPlaybackRecord`:单个连续录像区间及 RTSP 地址。 - `service/HikNvrPlaybackService`:参数校验、SDK 查询、区间裁剪与合并、SDK 通道到 RTSP 通道的衔接。 - `service/HikPlaybackUrlBuilder`:使用已解析的 RTSP 通道号和设备本地北京时间生成回放 URL。 - `com.inspect.nvr.service.HikLoginSession`:保存登录句柄及设备返回的模拟/数字通道布局,并提供 SDK 通道到 RTSP 通道的映射。 - `exception/HikNvrPlaybackException`:携带 SDK 错误码或参数错误信息。 - `controller/HikNvrPlaybackExceptionHandler`:将参数错误转换为 HTTP 400,将 NVR 登录或查询错误转换为 HTTP 502。 服务复用项目现有 `HikLoginService` 管理海康登录句柄,不重复创建独立登录缓存。登录缓存的值从单独的用户句柄扩展为 `HikLoginSession`,缓存时长、IP 键和登出策略保持不变。 ## SDK 查询流程 1. 校验请求参数并按北京时间解析查询范围。 2. 构造 `NvrInfo`,通过 `HikLoginService.loginSession` 获取复用的登录句柄和设备通道布局。 3. 构造 `NET_DVR_FILECOND_V40`: - `lChannel` 使用请求通道号。 - `dwFileType=0xff`,查询所有录像类型。 - `dwIsLocked=0xff`,查询锁定和未锁定录像。 - `byFindType=0`,查询普通录像卷。 - `byQuickSearch=0`,获取完整文件信息。 - 主码流请求映射为 SDK `byStreamType=3`,子码流请求映射为 SDK `byStreamType=1`。 - 开始和结束时间按设备本地北京时间填写 `NET_DVR_TIME`。 4. 调用 `NET_DVR_FindFile_V40` 获取查询句柄;返回负数时读取 `NET_DVR_GetLastError` 并报错。 5. 循环调用 `NET_DVR_FindNextFile_V40`: - `NET_DVR_FILE_SUCCESS`:读取录像文件起止时间。 - `NET_DVR_ISFINDING`:短暂等待后继续,整体设置有限等待次数,避免请求无限阻塞。 - `NET_DVR_FILE_NOFIND` 或 `NET_DVR_NOMOREFILE`:正常结束查询。 - `NET_DVR_FILE_EXCEPTION` 或 `-1`:读取 SDK 错误码并报错。 6. 在 `finally` 中调用 `NET_DVR_FindClose_V30` 关闭查询句柄。 ## 时间区间处理 - 将每个 SDK 录像区间裁剪到用户请求区间内,避免返回请求范围之外的时间。 - 丢弃裁剪后为空的区间。 - 按开始时间排序。 - 仅合并重叠或首尾完全相接的区间;存在真实时间间隔时分别返回,不能掩盖录像缺失。 - 返回时间使用北京时间 `yyyyMMddHHmmss`。 - RTSP 参数保留设备本地北京时间数字,格式为 `yyyyMMdd'T'HHmmss'Z'`。目标 NVR 固件实测把参数数字按设备本地时间解释;将北京时间转换为 UTC 会建立会话但收不到录像数据。 例如用户查询 `12:00:00~13:00:00`,NVR 只存在 `12:30:00~13:00:00`,返回: ```json "timeRange": ["20260713123000", "20260713130000"] ``` ## RTSP URL 录像查询继续使用 SDK 通道号。RTSP 轨道使用设备展示通道序号,两者不能直接混用。登录返回的 `NET_DVR_DEVICEINFO_V30` 提供模拟通道数、模拟起始通道、数字通道数和数字起始通道,映射规则为: ```text 数字通道:rtspChannel = analogChannelCount + (sdkChannel - digitalStartChannel) + 1 模拟通道:rtspChannel = (sdkChannel - analogStartChannel) + 1 trackId = rtspChannel * 100 + streamType ``` 例如纯 NVR 的模拟通道数为 `0`、数字起始通道为 `33`,则 SDK 通道 `33` 映射为 RTSP 通道 `1`,主码流轨道为 `101`。如果设备通道元数据无法覆盖请求通道,则保留原通道号作为兼容回退。 地址格式: ```text rtsp://<编码后的用户名>:<编码后的密码>@:/Streaming/tracks/?starttime=<北京时间开始>&endtime=<北京时间结束> ``` 账号和密码按照 URL user-info 规则进行百分号编码。服务端日志禁止输出完整请求对象、密码或最终 RTSP URL。 ## 错误处理 - 请求字段缺失、端口或通道非法、时间格式错误、结束时间不晚于开始时间:HTTP 400。 - SDK 登录失败、NVR 离线、通道错误、权限不足、查询失败或查询超时:HTTP 502,响应中包含 SDK 错误码和中文错误信息。 - 查询成功但没有录像:HTTP 200,`hasRecord=false`,`records=[]`。 - 查询句柄关闭失败只记录不包含敏感信息的警告,不覆盖原始查询结果或异常。 ## 测试 测试先于生产代码编写,覆盖: - 北京时间解析及 RTSP 设备本地时间格式化。 - 纯 NVR 和混合 DVR 的 SDK 通道到 RTSP 通道映射。 - 主、子码流轨道号和 SDK 码流类型映射。 - 用户名、密码特殊字符的安全编码。 - 查询范围内只有部分录像时,返回裁剪后的完整时间戳。 - 重叠和首尾相接区间被合并,存在时间缺口的区间保持分离。 - 无录像返回空列表。 - SDK 查找中状态能够有限重试。 - 登录、查询和通道错误返回对应错误码。 - 成功、无录像及异常路径都关闭查询句柄。 - Swagger 覆盖测试更新为包含新增接口、参数和响应模型。 ## 非目标 - 不转码为 HLS、FLV 或 MP4。 - 不在服务端代理 RTSP 码流。 - 不下载录像文件。 - 不修改现有海康登录缓存的过期时长、键和登出策略。 - 不提交或暂存任何文件。