package com.ard.work.device.camera.service; import com.ard.work.api.domian.ArdCamera; import com.ard.work.api.domian.ArdChannel; import com.ard.work.api.domian.CameraCmd; import com.ard.work.api.domian.PtzDto; import com.ard.work.device.camera.domain.PtzParamDTO; import com.ard.work.sdk.service.CameraSDKService; import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @Slf4j @Component @RequiredArgsConstructor public class PtzDataCollector { private final CameraSDKService cameraSDKService; private final PtzCacheManager ptzCacheManager; @Resource(name = "timeoutExecutor") private ThreadPoolTaskExecutor sdkExecutor; private static final Set EXCLUDE_FACTORY = new HashSet<>(Arrays.asList("7")); private static final String CAMERA_ENABLE = "1"; private static final Set SUPPORT_TYPES = new HashSet<>(Arrays.asList("1", "4")); /** 单通道 SDK 调用超时 */ private static final long SDK_CALL_TIMEOUT_MS = 2000L; /** 整相机所有通道总采集超时 */ private static final long CAMERA_TOTAL_TIMEOUT_MS = 3000L; @Async("cameraPTZExecutor") public void collectCamera(ArdCamera camera, Set runningCamera) { String camId = camera.getId(); try { if (!isValidCamera(camera)) return; List channels = camera.getChannelList(); if (channels == null || channels.isEmpty()) return; CompletableFuture[] futures = channels.stream() .map(ch -> CompletableFuture.runAsync( () -> collectChannel(camera, ch), sdkExecutor)) .toArray(CompletableFuture[]::new); try { CompletableFuture.allOf(futures).get(CAMERA_TOTAL_TIMEOUT_MS, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { log.warn("采集相机 {} 整体超时,部分通道数据可能未更新", camId); } catch (Exception e) { log.error("采集相机 {} 异常", camId, e); } } finally { runningCamera.remove(camId); } } private void collectChannel(ArdCamera camera, ArdChannel channel) { PtzParamDTO dto = fetchPtzWithTimeout(camera, channel); if (dto != null) { ptzCacheManager.updatePtzData(camera.getId() + "_" + channel.getChanNo(), dto); } } /** * 带超时的 SDK 调用,防止 SDK 卡死拖垮线程池 */ private PtzParamDTO fetchPtzWithTimeout(ArdCamera camera, ArdChannel channel) { CompletableFuture future = CompletableFuture.supplyAsync( () -> fetchPtz(camera, channel), sdkExecutor); try { return future.get(SDK_CALL_TIMEOUT_MS, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { log.warn("相机 {} 通道 {} SDK 调用超时", camera.getId(), channel.getChanNo()); future.cancel(true); return null; } catch (Exception e) { log.error("相机 {} 通道 {} SDK 调用异常", camera.getId(), channel.getChanNo(), e); return null; } } private PtzParamDTO fetchPtz(ArdCamera camera, ArdChannel channel) { CameraCmd cmd = new CameraCmd(); cmd.setCameraId(camera.getId()); cmd.setChanNo(channel.getChanNo()); PtzParamDTO dto = cameraSDKService.getGisInfo(cmd); if (dto == null) return null; dto.setCameraId(camera.getId()); dto.setChanNo(channel.getChanNo()); dto.setLongitude(camera.getLongitude()); dto.setLatitude(camera.getLatitude()); dto.setAltitude(camera.getAltitude()); PtzDto ptz = cameraSDKService.getPtz(cmd); if (ptz != null) { if ("4".equals(camera.getType())) { ptz.setP((ptz.getP() + camera.getBaseYaw() + 360) % 360); } dto.setP(ptz.getP()); dto.setT(ptz.getT()); dto.setZ(ptz.getZ()); } return dto; } private boolean isValidCamera(ArdCamera camera) { return CAMERA_ENABLE.equals(camera.getState()) && !EXCLUDE_FACTORY.contains(camera.getFactory()) && SUPPORT_TYPES.contains(camera.getType()); } }