You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

209 lines
8.8 KiB

package com.inspect.nvr.controller;
import com.inspect.nvr.aop.TimeTrace;
import com.inspect.nvr.domain.device.*;
import com.inspect.nvr.domain.ivs.IvsPosition3dRequest;
import com.inspect.nvr.domain.ivs.IvsPtzControlRequest;
import com.inspect.nvr.domain.ivs.IvsPtzControlResponse;
import com.inspect.nvr.ivs.service.IvsOpenApiService;
import com.inspect.nvr.security.PtzLockService;
import com.inspect.nvr.service.IvsCameraService;
import com.inspect.nvr.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
@Slf4j
@RestController
public class IvsCameraController {
@Resource
private IvsCameraService ivsCameraService;
@Resource
private IvsOpenApiService ivsOpenApiService;
/** 云台控制权锁:body 携带角色字段的调用按 cameraCode 抢锁,北向机器调用跳过。 */
@Resource
private PtzLockService ptzLockService;
private static final Object captureLock = new Object();
private static final Object captureLock_pic = new Object();
/**
* 查询指定摄像机在对应 NVR 上的预置位列表。
*/
@CrossOrigin
@GetMapping("/device/ptzpresetlist/{cameraCode}/{nvrCode}")
public ResponseEntity<IvsPresetListView> ptzPresetList(
@PathVariable("cameraCode") String cameraCode,
@PathVariable("nvrCode") String nvrCode) {
log.info("PTZ_PRESET_LIST cameraCode : {}, nvrCode: {}", cameraCode, nvrCode);
IvsPresetListView ivsPresetListView = ivsOpenApiService.presetList(cameraCode, nvrCode);
return ResponseEntity
.ok()
.body(ivsPresetListView);
}
/**
* 接收 IVS 云台控制指令,并转发给厂商适配器执行。
*/
@CrossOrigin
@PostMapping({"/device/ptzcontrol"})
public ResponseEntity<IvsPtzControlResponse> ptzControl( @RequestBody IvsPtzControlRequest param) {
log.info("Ptz control request param: {}", param);
// 请求体携带 username 时按 cameraCode 校验控制权;锁被更高优先持有时抛 HTTP 423。
ptzLockService.checkLock(
param == null ? null : param.getCameraCode(),
param == null ? null : param.getUsername(),
param == null ? null : param.getJoinbrightToken());
return ResponseEntity.ok().body(ivsOpenApiService.ptzControl(param));
}
@PostMapping({"/v1/device/ptzcontrol"})
public ResponseEntity<PtzControlResult> ptzControl(@RequestBody PtzControlParam param) {
log.info("Ptz control request param: {}", param);
return ResponseEntity
.ok()
.body(ivsCameraService.ptzControl(param));
}
/**
* 接收前端视频框选坐标,并按框选方向执行 3D 定位放大或缩小。
*
* @param param cameraCode 和 0~255 归一化起止坐标
* @return 厂商设备的执行结果
*/
@CrossOrigin
@PostMapping("/device/position3d")
public ResponseEntity<IvsPtzControlResponse> position3d(
@RequestBody IvsPosition3dRequest param) {
// 3D 定位与普通云台控制共用同一把 cameraCode 控制权锁。
ptzLockService.checkLock(
param == null ? null : param.getCameraCode(),
param == null ? null : param.getUsername(),
param == null ? null : param.getJoinbrightToken());
// 由 IVS 门面按 cameraCode 选择厂商,并在厂商层完成坐标校验和 SDK 调用。
return ResponseEntity.ok().body(ivsOpenApiService.position3d(param));
}
/**
* 创建指定摄像机的平台抓图任务。
*/
@GetMapping({"/platform/platformSnapshot2/{cameraCode}/{domainCode}"})
public ResponseEntity<SnapshotCommandResult> platformSnapshot2(
@PathVariable("cameraCode") String cameraCode,
@PathVariable("domainCode") String domainCode) {
log.info("Platform Snapshot cameraCode: {}, domainCode: {}", cameraCode, domainCode);
return ResponseEntity
.ok()
.body(ivsCameraService.platformSnapshot(cameraCode, domainCode, null));
}
/**
* 按任务或时间范围查询平台抓图结果。
*/
@PostMapping({"/platform/snapshotList2"})
public ResponseEntity<SnapshotInfoListResult> retrieveSnapshotList2( @RequestBody SnapshotInfoListParam param) {
log.info("Snapshot list request param: {}", param);
return ResponseEntity
.ok()
.body(ivsCameraService.getSnapshotList(param));
}
/**
* 兼容旧版路径参数格式查询平台抓图结果。
*/
@GetMapping("/platform/snapshotlist/{cameraCode}/{domainCode}/{startTime}/{endTime}/{fromIndex}/{toIndex}/{snapType}")
public ResponseEntity<SnapshotInfoListResult> retrieveLegacySnapshotList(
@PathVariable("cameraCode") String cameraCode,
@PathVariable("domainCode") String domainCode,
@PathVariable("startTime") String startTime,
@PathVariable("endTime") String endTime,
@PathVariable("fromIndex") Integer fromIndex,
@PathVariable("toIndex") Integer toIndex,
@PathVariable("snapType") Integer snapType) {
SnapshotInfoListParam.SnapshotDetailInfo detail =
SnapshotInfoListParam.SnapshotDetailInfo.builder()
.startTime(startTime)
.endTime(endTime)
.fromIndex(fromIndex)
.toIndex(toIndex)
.snapType(snapType)
.build();
SnapshotInfoListParam request = SnapshotInfoListParam.builder()
.cameraCode(cameraCode)
.domainCode(domainCode)
.snapshotDetailInfo(detail)
.build();
return ResponseEntity.ok(ivsCameraService.getSnapshotList(request));
}
/**
* @deprecated
* use {@link #capture} instead.
*/
@Deprecated
/**
* 使用旧版会话标识下载抓图文件,并保留失败重试机制。
*/
@GetMapping("/downloadfile_old")
public ResponseEntity<InputStreamResource> downloadFile( @RequestParam("filesessionid") String fileSessionId) {
// synchronized (captureLock_pic) {
log.info("Download File Get Stream, fileSessionId: {}", fileSessionId);
ByteArrayInputStream byteArrayInputStream = null;
//添加重试机制
final int MAX_RETRIES = 20; // 最大重试次数
boolean success = false;
int retryCount = 0;
while (!success && retryCount < MAX_RETRIES) {
try {
retryCount++;
log.info("第{}次尝试抓图", retryCount);
byteArrayInputStream = ivsCameraService.downloadFile(fileSessionId);
if (StringUtils.isNotNull(byteArrayInputStream)) {
success = true;
} else {
Thread.sleep(500); // 等待一秒后重试
log.error("第{}次抓图失败", retryCount);
}
} catch (Exception e) {
log.error("第{}次抓图失败: {}", retryCount, e.getMessage());
}
}
return ResponseEntity
.ok()
.body(new InputStreamResource(byteArrayInputStream));
// }
}
@TimeTrace
/**
* 根据临时图片会话标识下载抓图文件。
*/
@GetMapping("/downloadfile")
public ResponseEntity<InputStreamResource> capture( @RequestParam("filesessionid") String fileSessionId) {
ByteArrayInputStream byteArrayInputStream = ivsCameraService.capture(fileSessionId);
return ResponseEntity.ok().body(new InputStreamResource(byteArrayInputStream));
}
@TimeTrace
/**
* 使用 Digest 认证直接抓取并返回 JPEG 图片。
*/
@GetMapping("/digest")
public ResponseEntity<byte[]> digest( @RequestParam("filesessionid") String fileSessionId) {
byte[] bytes = ivsCameraService.captureDigest(fileSessionId);
if (bytes == null || bytes.length == 0) {
log.error("Digest 抓图失败,filesessionid={}", fileSessionId);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
return ResponseEntity.ok()
.contentType(MediaType.IMAGE_JPEG)
.contentLength(bytes.length)
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"digest.jpg\"")
.body(bytes);
}
}