目录
一、问题分析
二、解决思路
三、贴代码
四、总结
一、问题分析
按惯例上问题:
-
设备告警采用高电平持续模式:一次开,不主动关就一直处于告警状态。
-
并发时多个请求下发
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;}
}
四、总结
- 每个设备串行执行(防止并发导致状态错乱)
- 自动“兜底”第二次关闸
- 不阻塞主线程(可支撑高并发请求)