?
目錄
一、問題分析
二、解決思路
三、貼代碼
四、總結?
一、問題分析
按慣例上問題:
-
設備告警采用高電平持續模式:一次開,不主動關就一直處于告警狀態。
-
并發時多個請求下發
setDVRAlarmOutConfig
,導致狀態混亂。 -
“開 -> 睡眠 -> 關” 這個鏈路若被中斷或未串聯,會導致設備長期處于告警狀態,后續指令失效。
-
有些設備存在 SDK 偶發失敗或無響應等問題。
二、解決思路
- ?避免并發打斷流程對每個
deviceId
加synchronized
鎖; - 設置告警時做 兩次兜底關
- 使用線程池異步執行告警任務
三、貼代碼
// 全局設備鎖容器
private static final ConcurrentHashMap<String, Object> deviceLocks = new ConcurrentHashMap<>();
// 告警線程池(你可以封裝成 @Async 或自己定義線程池)
private static final ExecutorService alarmExecutor = Executors.newFixedThreadPool(10);@GetMapping("autoAlarm")
@Operation(summary = "自動告警")
@Parameter(name = "deviceId", description = "設備ID", required = true)
@Parameter(name = "duration", description = "告警持續時間 單位 毫秒(不能超過10000毫秒)", required = true)
public CommonResult<String> autoAlarm(@RequestParam String deviceId, @RequestParam Integer duration) {Assert.isTrue(duration <= 10000, () -> new ServiceException("告警持續時間不能超過10000毫秒"));DeviceInfo info = SdkDevice.getDevice(deviceId);Assert.notNull(info, () -> new ServiceException(GlobalErrorCodeConstants.DEVICE_NOT_EXIST.getCode(),StrUtil.format("設備[{}]未注冊", deviceId)));Assert.isTrue(info.getLoginHandle().longValue() > 0,() -> new ServiceException(GlobalErrorCodeConstants.DEVICE_UN_REGISTERED.getCode(), "設備未注冊"));alarmExecutor.submit(() -> {Object lock = deviceLocks.computeIfAbsent(deviceId, k -> new Object());synchronized (lock) {try {// step 1. 開啟告警輸出NetSDKLib.CFG_ALARMOUT_INFO onConfig = DhSdkServer.getDVRAlarmOutConfig(info.getLoginHandle(), deviceId);log.info("[{}] 設置前 mode: {}", deviceId, onConfig.nOutputMode);boolean open = DhSdkServer.setDVRAlarmOutConfig(info.getLoginHandle(), onConfig, 1, info.getAlarmChannelId(), deviceId);if (!open) {log.warn("[{}] 告警打開失敗", deviceId);return;}log.info("[{}] 告警已打開,持續 {} 毫秒", deviceId, duration);ThreadUtil.sleep(duration);// step 2. 關閉告警輸出boolean firstClose = tryCloseAlarm(info, deviceId, "first");if (!firstClose) {ThreadUtil.sleep(200);tryCloseAlarm(info, deviceId, "fallback");}} catch (Exception ex) {log.error("[{}] 告警流程異常: {}", deviceId, ex.getMessage(), ex);}}});return CommonResult.success("告警任務已下發,后臺執行");
}private boolean tryCloseAlarm(DeviceInfo info, String deviceId, String phase) {try {NetSDKLib.CFG_ALARMOUT_INFO offConfig = DhSdkServer.getDVRAlarmOutConfig(info.getLoginHandle(), deviceId);boolean result = DhSdkServer.setDVRAlarmOutConfig(info.getLoginHandle(), offConfig, 2, info.getAlarmChannelId(), deviceId);log.info("[{}] {} 階段關閉告警結果: {}", deviceId, phase, result);return result;} catch (Exception e) {log.warn("[{}] {} 階段關閉異常: {}", deviceId, phase, e.getMessage());return false;}
}
四、總結?
- 每個設備串行執行(防止并發導致狀態錯亂)?
- 自動“兜底”第二次關閘
- 不阻塞主線程(可支撐高并發請求)