浏览代码

执法记录仪告警

bihuisong 2 天之前
父节点
当前提交
c91c666e5f

+ 86 - 0
src/main/java/com/sooka/sponest/monitor/camera/domain/CentermonitorTRecorder.java

@@ -0,0 +1,86 @@
+package com.sooka.sponest.monitor.camera.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.core.annotation.Excel;
+import com.ruoyi.common.core.web.domain.BaseEntity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.util.Date;
+
+/**
+ * 执法记录仪在离线时间对象 centermonitor_t_recorder
+ *
+ * @author ruoyi
+ * @date 2025-06-03
+ */
+public class CentermonitorTRecorder extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 摄像头编码
+     */
+    private String cameraCode;
+
+    /**
+     * 状态
+     */
+    @Excel(name = "状态")
+    private String status;
+
+    /**
+     * 在线时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "在线时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date onlineTime;
+
+    /**
+     * 离线时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "离线时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date offlineTime;
+
+    public void setCameraCode(String cameraCode) {
+        this.cameraCode = cameraCode;
+    }
+
+    public String getCameraCode() {
+        return cameraCode;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setOnlineTime(Date onlineTime) {
+        this.onlineTime = onlineTime;
+    }
+
+    public Date getOnlineTime() {
+        return onlineTime;
+    }
+
+    public void setOfflineTime(Date offlineTime) {
+        this.offlineTime = offlineTime;
+    }
+
+    public Date getOfflineTime() {
+        return offlineTime;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+                .append("cameraCode", getCameraCode())
+                .append("status", getStatus())
+                .append("onlineTime", getOnlineTime())
+                .append("offlineTime", getOfflineTime())
+                .toString();
+    }
+}

+ 71 - 0
src/main/java/com/sooka/sponest/monitor/camera/domain/CentermonitorTRecorderAlarm.java

@@ -0,0 +1,71 @@
+package com.sooka.sponest.monitor.camera.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.core.annotation.Excel;
+import com.ruoyi.common.core.web.domain.BaseEntity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.util.Date;
+
+/**
+ * 执法记录仪在离线时间对象 centermonitor_t_recorder
+ *
+ * @author ruoyi
+ * @date 2025-06-03
+ */
+public class CentermonitorTRecorderAlarm extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 摄像头编码
+     */
+    private String cameraCode;
+
+    /**
+     * 告警名称
+     */
+    @Excel(name = "告警名称")
+    private String alarmName;
+
+    /**
+     * 告警时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "在线时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date alarmTime;
+
+
+    public void setCameraCode(String cameraCode) {
+        this.cameraCode = cameraCode;
+    }
+
+    public String getCameraCode() {
+        return cameraCode;
+    }
+
+    public String getAlarmName() {
+        return alarmName;
+    }
+
+    public void setAlarmName(String alarmName) {
+        this.alarmName = alarmName;
+    }
+
+    public Date getAlarmTime() {
+        return alarmTime;
+    }
+
+    public void setAlarmTime(Date alarmTime) {
+        this.alarmTime = alarmTime;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+                .append("cameraCode", getCameraCode())
+                .append("alarmName", getAlarmName())
+                .append("alarmTime", getAlarmTime())
+                .toString();
+    }
+}

+ 16 - 0
src/main/java/com/sooka/sponest/monitor/camera/mapper/CentermonitorTCamerachannelMapper.java

@@ -0,0 +1,16 @@
+package com.sooka.sponest.monitor.camera.mapper;
+
+
+
+import java.util.List;
+
+/**
+ * 摄像头通道Mapper接口
+ *
+ * @author ruoyi
+ * @date 2022-06-10
+ */
+public interface CentermonitorTCamerachannelMapper {
+
+    String selectChannelCodeByDeviceCode(String deviceCode);
+}

+ 17 - 0
src/main/java/com/sooka/sponest/monitor/camera/mapper/CentermonitorTRecorderAlarmMapper.java

@@ -0,0 +1,17 @@
+package com.sooka.sponest.monitor.camera.mapper;
+
+
+import com.sooka.sponest.monitor.camera.domain.CentermonitorTRecorderAlarm;
+
+
+/**
+ * 执法记录仪在离线时间Mapper接口
+ *
+ * @author ruoyi
+ * @date 2025-06-03
+ */
+public interface CentermonitorTRecorderAlarmMapper {
+
+    void insertCentermonitorTRecorderAlarm(CentermonitorTRecorderAlarm centermonitorTRecorderAlarm);
+
+}

+ 28 - 0
src/main/java/com/sooka/sponest/monitor/camera/mapper/CentermonitorTRecorderMapper.java

@@ -0,0 +1,28 @@
+package com.sooka.sponest.monitor.camera.mapper;
+
+
+import com.sooka.sponest.monitor.camera.domain.CentermonitorTRecorder;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 执法记录仪在离线时间Mapper接口
+ *
+ * @author ruoyi
+ * @date 2025-06-03
+ */
+public interface CentermonitorTRecorderMapper {
+    /**
+     * 查询执法记录仪在离线时间
+     */
+   CentermonitorTRecorder selectCentermonitorTRecorder(@Param("deviceCode")String deviceCode);
+
+
+    void insertCentermonitorTRecorderBatch(Map<String, Object> map);
+
+    void updateCentermonitorTRecorderBatch(Map<String, Object> map);
+
+    String selectCameraCodeByChannelCode(@Param("cameraCode")String cameraCode);
+}

+ 117 - 23
src/main/java/com/sooka/sponest/monitor/dahua/controller/EquipmentStatusTaskController.java

@@ -7,10 +7,13 @@ import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.web.domain.AjaxResult;
 import com.ruoyi.common.log.annotation.Log;
 import com.ruoyi.common.log.enums.BusinessType;
+import com.sooka.sponest.monitor.camera.domain.CentermonitorTRecorder;
 import com.sooka.sponest.monitor.camera.mapper.CentermonitorTCameraMapper;
+import com.sooka.sponest.monitor.camera.mapper.CentermonitorTRecorderMapper;
 import com.sooka.sponest.monitor.dahua.domain.CentermonitorTMonitoringEquipmentPositionDO;
 import com.sooka.sponest.monitor.dahua.mapper.CentermonitorTMonitoringEquipmentPositionMapper;
 import com.sooka.sponest.monitor.dahua.service.DahuaService;
+import com.sooka.sponest.monitor.dahua.utils.DeviceMonitorManager;
 import com.sooka.sponest.monitor.dahua.utils.HttpEnum;
 import com.sooka.sponest.monitor.dahua.utils.HttpTestUtils;
 import com.sooka.sponest.monitor.remoteapi.service.lawenforcement.RemoteLawenforcementBaseService;
@@ -18,11 +21,14 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
-import org.springframework.beans.factory.annotation.Value;
+import org.apache.commons.lang3.ObjectUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -47,18 +53,22 @@ public class EquipmentStatusTaskController {
     private CentermonitorTCameraMapper centermonitorTCameraMapper;
     @Resource
     private RemoteLawenforcementBaseService remoteLawenforcementBaseService;
-    @Value("${sooka.dahua_interface_server.loginIp}")
-    private String loginIp;
-    @Value("${sooka.dahua_interface_server.loginPort}")
-    private String loginPort;
-    @Value("${sooka.dahua_interface_server.userName}")
-    private String userName;
-    @Value("${sooka.dahua_interface_server.userPwd}")
-    private String userPwd;
-//    private String loginIp = "10.53.0.35";
-//    private String loginPort = "7901";
-//    private String userName = "system";
-//    private String userPwd = "Admin123";
+    @Resource
+    private CentermonitorTRecorderMapper centermonitorTRecorderMapper;
+    @Autowired
+    private DeviceMonitorManager deviceMonitorManager;
+    //    @Value("${sooka.dahua_interface_server.loginIp}")
+//    private String loginIp;
+//    @Value("${sooka.dahua_interface_server.loginPort}")
+//    private String loginPort;
+//    @Value("${sooka.dahua_interface_server.userName}")
+//    private String userName;
+//    @Value("${sooka.dahua_interface_server.userPwd}")
+//    private String userPwd;
+    private String loginIp = "10.53.0.35";
+    private String loginPort = "7901";
+    private String userName = "system";
+    private String userPwd = "Admin123";
     //按组织获取设备详细信息
     public static final String ACTION = "/videoService/devicesManager/devicesInfo";
     private List<String> orgCodes = Arrays.asList("11033445593778368", "11248668755298496");
@@ -67,7 +77,7 @@ public class EquipmentStatusTaskController {
     @Resource
     CentermonitorTMonitoringEquipmentPositionMapper centermonitorTMonitoringEquipmentPositionMapper;
 
-//    @Scheduled(cron = "0/5 * * * * ?")
+    //    @Scheduled(cron = "0/5 * * * * ?")
     @Log(title = "对接大华根据组织获取执法记录仪、单兵设备在线状态定时任务", businessType = BusinessType.OTHER)
     @ApiOperation(value = "对接大华根据组织获取执法记录仪、单兵设备在线状态定时任务", notes = "对接大华根据组织获取执法记录仪、单兵设备在线状态定时任务")
     @RequestMapping(value = "/getLongPollingPositionMsg", method = GET)
@@ -146,27 +156,111 @@ public class EquipmentStatusTaskController {
                 }
             } else {
                 //向执法中心推送设备状态
-                R result = remoteLawenforcementBaseService.deviceOpenOrClose(arr);
-                if (result.getCode() != 200) {
-                    log.info("向执法中心推送设备状态失败:{}", result.getMsg());
-                }
+//                R result = remoteLawenforcementBaseService.deviceOpenOrClose(arr);
+//                if (result.getCode() != 200) {
+//                    log.info("向执法中心推送设备状态失败:{}", result.getMsg());
+//                }
+
+                //误操作开机,导致设备状态非正常使用,进行告警处理
+                saveOrUpdateRecorderStatus(arr);
+
                 //更新执法仪在离线状态
-                centermonitorTCameraMapper.updateBatchLawEnforcementInstrumentState(arr);
+//                centermonitorTCameraMapper.updateBatchLawEnforcementInstrumentState(arr);
+            }
+        }
+    }
+
+    //保存或更新执法记录仪开关机记录
+    private void saveOrUpdateRecorderStatus(List<Map<String, Object>> arr) {
+        for (Map<String, Object> var : arr) {
+            Map<String, Object> map = new HashMap<>();
+            String deviceCode = var.get("code").toString();
+            map.put("cameraCode", deviceCode);
+            //设备状态,”0” : 不在线 “1” : 在线,保存入库和执法仪状态数值同步
+            map.put("status", var.get("status").equals("1") ? "0" : "1");
+            if (deviceCode.equals("11151730914298053")) {
+                System.out.println("设备是否在线:" + (var.get("status").equals("1") ? "在线" : "离线"));
+                System.out.println("设备在离线时间:" + deviceMonitorManager.formatStatusTime(var));
+            }
+            if (var.get("status").equals("0")) {
+                map.put("onlineTime", null);
+                map.put("offlineTime", deviceMonitorManager.formatStatusTime(var));
+            } else {
+                map.put("onlineTime", deviceMonitorManager.formatStatusTime(var));
+                map.put("offlineTime", null);
+
+                // 设备上线时,如果还没有监控任务,则启动新任务
+                if (!deviceMonitorManager.hasMonitoringTask(deviceCode)) {
+                    try {
+                        deviceMonitorManager.startDeviceMonitoringIfAbsent(deviceCode, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(deviceMonitorManager.formatStatusTime(var)));
+                    } catch (ParseException e) {
+                        log.error("日期格式转换异常", e);
+                    }
+                }
+            }
+            CentermonitorTRecorder centermonitorTRecorder = centermonitorTRecorderMapper.selectCentermonitorTRecorder(var.get("code").toString());
+            if (ObjectUtils.isEmpty(centermonitorTRecorder)) {
+                centermonitorTRecorderMapper.insertCentermonitorTRecorderBatch(map);
+            } else {
+                centermonitorTRecorderMapper.updateCentermonitorTRecorderBatch(map);
+            }
+
+            if (var.get("status").equals("0")) {
+                deviceMonitorManager.stopDeviceMonitoring(deviceCode);
             }
         }
     }
 
     /**
-     *获取记录仪位置数据
+     * 执法记录仪设备开机
+     *
+     * @param cameraCode 摄像头编码
+     * @throws Exception
+     */
+    @RequestMapping(value = "/equipStartup/{cameraCode}", method = GET)
+    public void equipStartup(@PathVariable("cameraCode") String cameraCode) {
+        JSONObject json = deviceMonitorManager.getJsonObject(cameraCode, "1");
+        if (ObjectUtils.isEmpty(json)) {
+            log.info("获取设备信息失败,请检查设备编码:{}", cameraCode);
+            return;
+        }
+        deviceMonitorManager.stopDeviceMonitoring(json.get("code").toString());
+    }
+
+
+    /**
+     * 执法记录仪设备关机
+     *
+     * @param cameraCode 摄像头编码
+     * @throws Exception
+     */
+    @RequestMapping(value = "/equipShutdown/{cameraCode}", method = GET)
+    public void equipShutdown(@PathVariable("cameraCode") String cameraCode) {
+        //根据通道编码(摄像头编码)获取大华设备info信息
+        JSONObject json = deviceMonitorManager.getJsonObject(cameraCode, "1");
+
+        //传来关机信号,如果设备未关机,则添加告警定时任务
+        //设备状态,”0” : 不在线 “1” : 在线
+        if (!json.get("status").equals("0")) {
+            //传递关机信号,状态为0,则添加告警定时任务
+            deviceMonitorManager.startShutdownMonitoring(json.get("code").toString(), "0");
+        }
+
+    }
+
+
+    /**
+     * 获取记录仪位置数据
      */
     @PostMapping("putDahuaRecorderLocation")
     @ResponseBody
-    public AjaxResult insertLocationListToday(@RequestBody CentermonitorTMonitoringEquipmentPositionDO centermonitorTMonitoringEquipmentPositionDO) {
+    public AjaxResult insertLocationListToday(@RequestBody CentermonitorTMonitoringEquipmentPositionDO
+                                                      centermonitorTMonitoringEquipmentPositionDO) {
         try {
             dahuaService.insertEquipmentPosition(centermonitorTMonitoringEquipmentPositionDO);
             //更新执法记录仪经纬度
-            log.info("获取记录仪位置数据:{}",centermonitorTMonitoringEquipmentPositionDO);
-            centermonitorTMonitoringEquipmentPositionMapper.updateLongitudeAndLatitudeByCameraCode(centermonitorTMonitoringEquipmentPositionDO.getGpsX(),centermonitorTMonitoringEquipmentPositionDO.getGpsY(),centermonitorTMonitoringEquipmentPositionDO.getChannelCode());
+            log.info("获取记录仪位置数据:{}", centermonitorTMonitoringEquipmentPositionDO);
+            centermonitorTMonitoringEquipmentPositionMapper.updateLongitudeAndLatitudeByCameraCode(centermonitorTMonitoringEquipmentPositionDO.getGpsX(), centermonitorTMonitoringEquipmentPositionDO.getGpsY(), centermonitorTMonitoringEquipmentPositionDO.getChannelCode());
             return AjaxResult.success();
         } catch (Exception e) {
             e.printStackTrace();

+ 264 - 0
src/main/java/com/sooka/sponest/monitor/dahua/utils/DeviceMonitorManager.java

@@ -0,0 +1,264 @@
+package com.sooka.sponest.monitor.dahua.utils;
+
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.nacos.shaded.com.google.gson.Gson;
+import com.ruoyi.common.core.domain.R;
+import com.sooka.sponest.monitor.camera.domain.CentermonitorTRecorderAlarm;
+import com.sooka.sponest.monitor.camera.mapper.CentermonitorTCamerachannelMapper;
+import com.sooka.sponest.monitor.camera.mapper.CentermonitorTRecorderAlarmMapper;
+import com.sooka.sponest.monitor.camera.mapper.CentermonitorTRecorderMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PreDestroy;
+import javax.annotation.Resource;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.*;
+
+@Slf4j
+@Component
+public class DeviceMonitorManager {
+
+    @Resource
+    private CentermonitorTRecorderMapper centermonitorTRecorderMapper;
+    @Resource
+    private CentermonitorTRecorderAlarmMapper centermonitorTRecorderAlarmMapper;
+    @Resource
+    private CentermonitorTCamerachannelMapper centermonitorTCamerachannelMapper;
+    //    @Value("${sooka.dahua_interface_server.loginIp}")
+//    private String loginIp;
+//    @Value("${sooka.dahua_interface_server.loginPort}")
+//    private String loginPort;
+//    @Value("${sooka.dahua_interface_server.userName}")
+//    private String userName;
+//    @Value("${sooka.dahua_interface_server.userPwd}")
+//    private String userPwd;
+    private String loginIp = "10.53.0.35";
+    private String loginPort = "7901";
+    private String userName = "system";
+    private String userPwd = "Admin123";
+    //按组织获取设备详细信息
+    public static final String ACTION = "/videoService/devicesManager/devicesInfo";
+    // 使用线程安全的Map存储所有设备的监控任务
+    private final ConcurrentMap<String, ScheduledFuture<?>> deviceTasks = new ConcurrentHashMap<>();
+    // 定时任务线程池
+    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(20);
+    // 存储设备初始状态
+    private final ConcurrentMap<String, String> deviceInitialStatus = new ConcurrentHashMap<>();
+    // 存储设备状态检查开始时间
+    private final ConcurrentMap<String, Long> monitoringStartTimes = new ConcurrentHashMap<>();
+
+    /**
+     * 启动设备开机监控任务(如果不存在)
+     *
+     * @param deviceCode 设备编码
+     * @param onlineTime 上线时间
+     * @return true表示成功启动任务,false表示任务已存在
+     */
+    public boolean startDeviceMonitoringIfAbsent(String deviceCode, Date onlineTime) {
+        // 如果已有监控任务,直接返回false
+        if (deviceTasks.containsKey(deviceCode)) {
+            return false;
+        }
+        // 创建监控任务
+        Runnable monitorTask = () -> {
+            checkDeviceStatus(deviceCode, onlineTime);
+        };
+        // 每分钟检查一次
+        ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(
+                monitorTask, 0, 1, TimeUnit.MINUTES);
+        // 原子性操作,防止并发问题
+        ScheduledFuture<?> existing = deviceTasks.putIfAbsent(deviceCode, future);
+        if (existing != null) {
+            // 如果已经有其他线程创建了任务,取消当前创建的任务
+            future.cancel(false);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 检查设备是否有监控任务
+     *
+     * @param deviceCode 设备编码
+     * @return 是否已有监控任务
+     */
+    public boolean hasMonitoringTask(String deviceCode) {
+        return deviceTasks.containsKey(deviceCode);
+    }
+
+    /**
+     * 停止设备的监控任务
+     *
+     * @param deviceCode 设备编码
+     */
+    public void stopDeviceMonitoring(String deviceCode) {
+        ScheduledFuture<?> future = deviceTasks.remove(deviceCode);
+        if (future != null) {
+            future.cancel(false);
+        }
+    }
+
+    /**
+     * 检查设备状态
+     */
+    private void checkDeviceStatus(String deviceCode, Date onlineTime) {
+        // 这里实现你的状态检查逻辑
+        Date now = new Date();
+        long offlineMinutes = (now.getTime() - onlineTime.getTime()) / (60 * 1000);
+        if (offlineMinutes >= 5) {
+            System.out.println("设备 " + deviceCode + " 已在线超过5分钟,触发报警!");
+            triggerAlarm(deviceCode, onlineTime,"设备开机,未执法告警");
+            stopDeviceMonitoring(deviceCode);
+        }
+    }
+
+    /**
+     * 触发报警
+     */
+    private void triggerAlarm(String deviceCode, Date onlineTime,String alarmName) {
+        String cameraCode = centermonitorTCamerachannelMapper.selectChannelCodeByDeviceCode(deviceCode);
+        CentermonitorTRecorderAlarm saveVo = new CentermonitorTRecorderAlarm();
+        saveVo.setCameraCode(cameraCode);
+        saveVo.setAlarmName(alarmName);
+        saveVo.setAlarmTime(onlineTime);
+        centermonitorTRecorderAlarmMapper.insertCentermonitorTRecorderAlarm(saveVo);
+        System.out.println("设备编码:" + deviceCode + "摄像头编码:" + cameraCode + "执行报警操作.");
+    }
+
+
+    /**
+     * 启动设备关机监控任务
+     *
+     * @param deviceCode    摄像头编码
+     * @param currentStatus 当前设备状态
+     * @return true表示成功启动任务,false表示任务已存在
+     */
+    public boolean startShutdownMonitoring(String deviceCode, String currentStatus) {
+        if (deviceTasks.containsKey(deviceCode)) {
+            return false;
+        }
+        // 记录初始状态
+        deviceInitialStatus.put(deviceCode, currentStatus);
+        // 记录开始关机时间
+        monitoringStartTimes.put(deviceCode, System.currentTimeMillis());
+
+        Runnable shutdownMonitorTask = () -> {
+            checkDeviceStatusChange(deviceCode);
+        };
+        ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(
+                shutdownMonitorTask, 0, 1, TimeUnit.MINUTES);
+
+        ScheduledFuture<?> existing = deviceTasks.putIfAbsent(deviceCode, future);
+        if (existing != null) {
+            future.cancel(false);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 检查设备状态变化
+     */
+    private void checkDeviceStatusChange(String deviceCode) {
+        // 1. 状态变化检查
+        JSONObject jsonObject = queryDeviceStatus(deviceCode);
+        String initialStatus = deviceInitialStatus.get(deviceCode);
+        if (jsonObject.get("status").equals(initialStatus)) {
+            System.out.println("设备已经关机,停止当前监控任务");
+            stopDeviceMonitoring(deviceCode);
+        }
+        // 2. 强制检查时间阈值(即使状态已变化也记录时长)
+        Long startTime = monitoringStartTimes.get(deviceCode);
+        if (startTime == null) return;
+
+        long elapsedMinutes = (System.currentTimeMillis() - startTime) / (60 * 1000);
+        if (elapsedMinutes >= 5) {
+            triggerAlarm(deviceCode, new Date(),"设备执法完成未关机,告警");
+            System.out.println("设备 " + deviceCode + " 触发关机按钮,超过5分钟内状态未变化,触发报警");
+            stopDeviceMonitoring(deviceCode);
+            return;
+        }
+    }
+
+    /**
+     * 查询设备状态(需要根据实际情况实现)
+     */
+    private JSONObject queryDeviceStatus(String deviceCode) {
+        JSONObject jsonObject = new JSONObject();
+        try {
+            jsonObject = getJsonObject(deviceCode, "2");
+        } catch (Exception e) {
+            log.error("根据通道编码(摄像头编码)获取大华设备info信息失败:", e);
+        }
+        return jsonObject; // 实际应该调用设备服务查询状态
+    }
+
+    /**
+     * 根据设备编码或者摄像头编码获取大华设备info信息
+     *
+     * @param code (摄像头编码或者设备编码)
+     * @param type (1:摄像头编码,2:设备编码)
+     * @return
+     * @throws Exception
+     */
+    public JSONObject getJsonObject(String code, String type) {
+        R<?> result = null;
+        try {
+            result = HttpTestUtils.getToken(loginIp, Integer.parseInt(loginPort), userName, userPwd);
+        } catch (Exception e) {
+            log.info("获取token失败,请检查配置信息:", JSONObject.parseObject(result.getMsg()));
+        }
+        String token = result.getData().toString();
+        Map<String, String> content = new HashMap<>();
+        content.put("orgCode", "11033445593778368");
+        String response = HttpTestUtils.httpRequest(HttpEnum.POST, loginIp, Integer.parseInt(loginPort), ACTION, token, new Gson().toJson(content));
+        Map<String, Object> rsp = new Gson().fromJson(response, Map.class);
+        List<Map<String, Object>> arr = (List<Map<String, Object>>) rsp.get("devices");
+        JSONObject json = new JSONObject();
+        final String deviceCode = type.equals("1")
+                ? centermonitorTRecorderMapper.selectCameraCodeByChannelCode(code)
+                : code;
+        arr.stream().filter(item -> item.get("code").equals(deviceCode)).findFirst().ifPresent(item -> {
+            json.put("code", item.get("code"));
+            json.put("status", item.get("status"));
+            json.put("statusTime", item.get("statusTime"));
+        });
+        return json;
+    }
+
+    //时间戳格式化为年月日时分秒
+    public String formatStatusTime(Map<String, Object> var) {
+        // 定义输出格式
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        // 从Map中获取double类型时间戳
+        double timestamp = (Double) var.get("statusTime");
+        // 将double转换为long(毫秒时间戳)
+        long milliseconds = (long) timestamp;
+        // 转换为Instant
+        Instant instant = Instant.ofEpochMilli(milliseconds);
+        // 转换为本地时区的日期时间(可根据需要修改时区)
+        LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+        return dateTime.format(formatter);
+    }
+
+    /**
+     * 应用关闭时清理资源
+     */
+    @PreDestroy
+    public void shutdown() {
+        scheduler.shutdownNow();
+        deviceTasks.clear();
+        deviceInitialStatus.clear();
+        monitoringStartTimes.clear();
+    }
+}

+ 15 - 0
src/main/resources/mapper/monitor/camera/CentermonitorTCamerachannelMapper.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.sooka.sponest.monitor.camera.mapper.CentermonitorTCamerachannelMapper">
+
+    <select id="selectChannelCodeByDeviceCode" resultType="String">
+        SELECT
+            channel_code
+        FROM
+            centermonitor_t_camerachannel
+        WHERE
+            device_code = #{deviceCode}
+    </select>
+</mapper>

+ 18 - 0
src/main/resources/mapper/monitor/camera/CentermonitorTRecorderAlarmMapper.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.sooka.sponest.monitor.camera.mapper.CentermonitorTRecorderAlarmMapper">
+
+    <resultMap type="CentermonitorTRecorderAlarm" id="CentermonitorTRecorderAlarmResult">
+        <result property="cameraCode" column="camera_code"/>
+        <result property="alarmName" column="alarm_name"/>
+        <result property="alarmTime" column="alarm_time"/>
+    </resultMap>
+
+    <insert id="insertCentermonitorTRecorderAlarm">
+        INSERT INTO centermonitor_t_recorder_alarm (camera_code, alarm_name, alarm_time)
+        VALUES (#{cameraCode}, #{alarmName}, #{alarmTime})
+    </insert>
+
+</mapper>

+ 54 - 0
src/main/resources/mapper/monitor/camera/CentermonitorTRecorderMapper.xml

@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.sooka.sponest.monitor.camera.mapper.CentermonitorTRecorderMapper">
+
+    <resultMap type="CentermonitorTRecorder" id="CentermonitorTRecorderResult">
+        <result property="cameraCode" column="camera_code"/>
+        <result property="status" column="status"/>
+        <result property="onlineTime" column="online_time"/>
+        <result property="offlineTime" column="offline_time"/>
+    </resultMap>
+
+    <select id="selectCentermonitorTRecorder" parameterType="CentermonitorTRecorder"
+            resultMap="CentermonitorTRecorderResult">
+        SELECT
+        camera_code,
+        status,
+        online_time,
+        offline_time
+        FROM
+        centermonitor_t_recorder rr
+        LEFT JOIN centermonitor_t_camerachannel cc ON rr.camera_code = channel_code
+        WHERE
+        device_code = #{deviceCode}
+        LIMIT 1
+    </select>
+
+    <insert id="insertCentermonitorTRecorderBatch">
+        INSERT INTO centermonitor_t_recorder (camera_code, status, online_time, offline_time)
+        SELECT cc.channel_code, r.status, r.online_time, r.offline_time
+        FROM (
+            SELECT
+            #{cameraCode} AS camera_code,
+            #{status} AS status,
+            #{onlineTime} AS online_time,
+            #{offlineTime} AS offline_time
+        ) r
+        JOIN centermonitor_t_camerachannel cc ON cc.device_code = r.camera_code
+    </insert>
+
+    <update id="updateCentermonitorTRecorderBatch">
+        update centermonitor_t_recorder
+        set status       = #{status},
+            online_time  = #{onlineTime},
+            offline_time = #{offlineTime}
+        WHERE camera_code =
+              (select channel_code from centermonitor_t_camerachannel where device_code = #{cameraCode})
+    </update>
+
+    <select id="selectCameraCodeByChannelCode" resultType="String">
+        select device_code from centermonitor_t_camerachannel where channel_code = #{cameraCode}
+    </select>
+</mapper>