liusuyi
2026-05-30 f4f4fc53260eb67483dce406a963628273786a61
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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<String> EXCLUDE_FACTORY = new HashSet<>(Arrays.asList("7"));
    private static final String CAMERA_ENABLE = "1";
    private static final Set<String> 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<String> runningCamera) {
        String camId = camera.getId();
        try {
            if (!isValidCamera(camera)) return;
            List<ArdChannel> 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<PtzParamDTO> 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());
    }
}