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.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.HikNvrCredentialService; import com.inspect.nvr.hik.service.HikNvrLiveStreamService; import com.inspect.nvr.hik.service.HikNvrUiActionService; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @CrossOrigin @RequestMapping("/hik/nvr/playback/hls/ui/actions") public class HikNvrUiActionController { /** 海康云台、预置位和抓图操作服务。 */ @Resource private HikNvrUiActionService actionService; /** 海康实时拉流服务。 */ @Resource private HikNvrLiveStreamService liveStreamService; /** 按页面选择的 NVR IP 从数据库读取服务端凭据。 */ @Resource private HikNvrCredentialService credentialService; /** 大华工作台预置位保存能力。 */ @Resource private DahuaIvsVendorService dahuaVendorService; /** * 接收海康 NVR 云台控制指令。 */ @PostMapping("/ptz") /** 代理海康云台动作请求。 */ public ResponseEntity ptz(@RequestBody HikNvrUiActionRequest request) { // 绑定页面所选 NVR 后执行云台动作,避免相同通道号被发送到默认设备。 return ResponseEntity.ok(actionServiceFor(request).ptz(request)); } /** * 调用指定通道的海康预置位。 */ @PostMapping("/preset") /** 代理海康预置位调用请求。 */ public ResponseEntity preset(@RequestBody HikNvrUiActionRequest request) { // 绑定页面所选 NVR 后执行预置位。 return ResponseEntity.ok(actionServiceFor(request).preset(request)); } /** * 保存当前云台位置为指定的海康预置位。 */ @PostMapping("/preset/create") /** 代理海康预置位保存请求。 */ public ResponseEntity createPreset(@RequestBody HikNvrUiActionRequest request) { // 先按页面 NVR IP 读取数据库厂商,浏览器不能自行选择 SDK。 NvrInfo nvr = nvrInfoFor(request); // 大华工作台复用同一路由,由 NetSDK 保存页面指定编号的预置位。 if (isDahua(nvr.getNvrCompany())) { return ResponseEntity.ok(dahuaVendorService.createPreset(request)); } // 海康设备继续沿用原有 SDK/ISAPI 保存实现。 return ResponseEntity.ok(actionService.forNvr(nvr).createPreset(request)); } /** * 读取指定通道的海康预置位列表。 */ @GetMapping("/presets") public ResponseEntity listPresets( @RequestParam("channel") Integer channel, @RequestParam("nvrIp") String nvrIp) { // 创建内部请求对象并读取 NVR 当前预置位列表。 HikNvrUiActionRequest request = new HikNvrUiActionRequest(); request.setNvrIp(nvrIp); request.setChannel(channel); // 绑定页面所选 NVR 后委托海康 ISAPI 列表读取能力。 return ResponseEntity.ok(actionServiceFor(request).listPresets(request)); } /** * 抓取海康 NVR 当前通道的 JPEG 图片。 */ @PostMapping(value = "/snapshot", produces = MediaType.IMAGE_JPEG_VALUE) /** 代理海康 JPEG 抓图请求。 */ public ResponseEntity snapshot(@RequestBody HikNvrUiActionRequest request) { // 绑定页面所选 NVR 后调用海康抓图服务获取 JPEG 字节。 byte[] image = actionServiceFor(request).snapshot(request); // 从请求中提取通道号用于响应文件名和响应头。 int channel = request == null || request.getChannel() == null ? 0 : request.getChannel(); String filename = "nvr-ch" + channel + "-" + System.currentTimeMillis() + ".jpg"; return ResponseEntity.ok() .contentType(MediaType.IMAGE_JPEG) .contentLength(image.length) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") .header("X-Capture-Channel", String.valueOf(channel)) .body(image); } /** * 启动指定通道的实时拉流任务。 */ @PostMapping("/live/start") /** 代理实时拉流启动请求。 */ public ResponseEntity startLive(@RequestBody HikNvrUiActionRequest request) { // 从页面请求提取设备树完整 cameraCode 和可选码流类型。 String cameraCode = request == null ? null : request.getCameraCode(); Integer streamType = request == null ? null : request.getStreamType(); // 实时服务通过现有 IVS 媒体接口解析 cameraCode 并取得 RTSP 源地址。 return ResponseEntity.ok(liveStreamService.start(cameraCode, streamType)); } /** * 停止指定通道的实时拉流任务。 */ @PostMapping("/live/stop") /** 代理实时拉流停止请求。 */ public ResponseEntity stopLive(@RequestBody HikNvrUiActionRequest request) { // 从页面请求提取要停止的完整 cameraCode。 String cameraCode = request == null ? null : request.getCameraCode(); // 只停止该 cameraCode 对应的流,不影响设备树中的其他摄像机。 return ResponseEntity.ok(liveStreamService.stop(cameraCode)); } /** * 查询指定通道的实时拉流状态。 */ @GetMapping("/live/status") public ResponseEntity liveStatus( @RequestParam("cameraCode") String cameraCode) { // 按设备树完整 cameraCode 查询唯一实时拉流状态。 return ResponseEntity.ok(liveStreamService.status(cameraCode)); } /** * 为页面请求绑定数据库中已启用的目标 NVR。 * * @param request 页面操作请求,必须包含所选 NVR IP * @return 绑定数据库目标 NVR 的海康操作服务 */ private HikNvrUiActionService actionServiceFor(HikNvrUiActionRequest request) { return actionService.forNvr(nvrInfoFor(request)); } /** 按页面请求读取并校验数据库中的目标 NVR。 */ private NvrInfo nvrInfoFor(HikNvrUiActionRequest request) { // 多 NVR 环境必须明确目标 IP,禁止按固定配置或隐式默认设备执行控制。 if (request == null || !hasText(request.getNvrIp())) { throw HikNvrPlaybackException.badRequest("NVR IP不能为空"); } // 只允许绑定数据库中已启用的 NVR,页面无法直接提供账号密码。 return credentialService.resolveByIp(request.getNvrIp().trim()); } /** 判断数据库厂商字段是否表示大华。 */ private boolean isDahua(String vendor) { return vendor != null && (vendor.trim().toUpperCase().contains("DAHUA") || vendor.contains("大华")); } /** * 判断页面提交的 NVR IP 是否包含有效文本。 * * @param value 待检查文本 * @return 非空且不全为空白时返回 true */ private boolean hasText(String value) { // 空文本不能定位数据库 NVR。 return value != null && !value.trim().isEmpty(); } }