Explorar el Código

Merge branch 'video-all' into master-new

Zimo hace 21 horas
padre
commit
8e3dba09af

+ 1 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/alarm/vo/IotVideoAlarmPageReqVO.java

@@ -71,4 +71,5 @@ public class IotVideoAlarmPageReqVO extends PageParam {
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] createTime;
 
+    private String businessChannel;
 }

+ 1 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/alarm/vo/IotVideoAlarmRespVO.java

@@ -88,4 +88,5 @@ public class IotVideoAlarmRespVO {
     @ExcelProperty("创建时间")
     private LocalDateTime createTime;
 
+    private String businessChannel;
 }

+ 3 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/alarm/vo/IotVideoAlarmSaveReqVO.java

@@ -59,6 +59,9 @@ public class IotVideoAlarmSaveReqVO {
     @JsonProperty("channelID")
     private String channelId;
 
+    @Schema(description = "业务通道id")
+    private String businessChannel;
+
     @Schema(description = "通道名称", example = "赵六")
     private String channelName;
 

+ 15 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/utils/XmlUtil.java

@@ -9,6 +9,9 @@ import org.dom4j.io.SAXReader;
 
 import javax.sip.RequestEvent;
 import javax.sip.message.Request;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
 import java.io.ByteArrayInputStream;
 import java.io.StringReader;
 import java.util.*;
@@ -115,4 +118,16 @@ public class XmlUtil {
         return xml.getRootElement();
     }
 
+    public static <T> T parseXml(String xml, Class<T> clazz) {
+        try {
+            // 创建 JAXB 上下文
+            JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
+            // 创建解组器
+            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
+            // 解析 XML 字符串
+            return clazz.cast(unmarshaller.unmarshal(new StringReader(xml)));
+        } catch (JAXBException e) {
+            throw new RuntimeException("XML 解析失败: " + e.getMessage(), e);
+        }
+    }
 }

+ 1 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/dataobject/alarm/IotVideoAlarmDO.java

@@ -99,4 +99,5 @@ public class IotVideoAlarmDO extends BaseDO {
      * 事件图片
      */
     private String imageUrl;
+    private String businessChannel;
 }

+ 83 - 10
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/hik/HikIsapiService.java

@@ -1,22 +1,23 @@
 package cn.iocoder.yudao.module.pms.hik;
 
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.IotVideoAlarmRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.IotVideoAlarmSaveReqVO;
-import cn.iocoder.yudao.module.pms.dal.dataobject.alarm.IotVideoAlarmDO;
+import cn.iocoder.yudao.module.pms.controller.admin.yanfan.utils.XmlUtil;
 import cn.iocoder.yudao.module.pms.service.alarm.IotVideoAlarmService;
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.Data;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.dom4j.Document;
+import org.dom4j.Element;
+import org.dom4j.QName;
+import org.dom4j.io.SAXReader;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.io.IOException;
-import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
+import java.io.StringReader;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
 
 @Slf4j
 @Service
@@ -51,7 +52,25 @@ public class HikIsapiService {
         log.info("订阅成功: deviceId={}, response={}", deviceId, response);
         return response;
     }
-    
+
+    /**
+     * 获取sip信息
+     * @param deviceId
+     * @return
+     * @throws IOException
+     */
+    public String getSipInfo(String deviceId) throws IOException {
+        HikvisionProperties.IsapiConfig.DeviceConfig device = getDeviceConfig(deviceId);
+        String url = device.getBaseUrl() + device.getPaths().getSipInfo();
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Content-Type", "application/json");
+
+        String response = httpClient.doGet(url, device.getUsername(), device.getPassword(),
+                device.getTimeout(), headers);
+        return response;
+    }
+
     /**
      * 构建订阅JSON
      */
@@ -139,12 +158,22 @@ public class HikIsapiService {
                 jsonPayload, IotVideoAlarmSaveReqVO.class);
             //安全帽检测与抽烟打电话
             if ("safetyHelmetDetection".equals(alarmEvent.getEventType()) || "抽烟打电话".equals(alarmEvent.getMpName())) {
+                //调用sip获取是哪个业务通道
+                String sipInfo = getSipInfo("nvr-001");
+                List<VideoInput> videoInputs = parseVideoInputList(sipInfo);
+                AtomicReference<String> businessChannelId = new AtomicReference<>();
+                videoInputs.stream().filter(e -> String.valueOf(e.getId()).equals(alarmEvent.getChannelId())).findFirst().ifPresent(e -> {
+                    businessChannelId.set(e.getVideoInputID());
+                });
+                if (businessChannelId.get() != null) {
+                    alarmEvent.setBusinessChannel(businessChannelId.get());
+                }
                 // 保存到数据库
                 iotVideoAlarmService.createIotVideoAlarm(alarmEvent);
                 // 触发告警处理流程
                 alarmEventService.processAlarmEvent(alarmEvent);
             }
-        } catch (JsonProcessingException e) {
+        } catch (Exception e) {
             log.error("JSON解析失败: {}", jsonPayload, e);
             throw new RuntimeException("JSON解析失败", e);
         }
@@ -194,4 +223,48 @@ public class HikIsapiService {
             .findFirst()
             .orElseThrow(() -> new IllegalArgumentException("设备不存在: " + deviceId));
     }
+
+    private List<VideoInput> parseVideoInputList(String xmlStr) {
+        List<VideoInput> videoInputList = new ArrayList<>();
+        SAXReader reader = new SAXReader();
+
+        try {
+            // 读取 XML 字符串为 Document 对象
+            Document document = reader.read(new StringReader(xmlStr));
+            // 获取根节点(SIPInfo)
+            Element root = document.getRootElement();
+
+            // 1. 找到 VideoInputList 节点(带命名空间)
+            Element videoInputListElement = root.element(QName.get("VideoInputList", "http://www.isapi.org/ver20/XMLSchema"));
+            if (videoInputListElement == null) {
+                return videoInputList;
+            }
+
+            // 2. 遍历所有 VideoInput 子节点
+            Iterator<Element> videoInputIter = videoInputListElement.elementIterator(QName.get("VideoInput", "http://www.isapi.org/ver20/XMLSchema"));
+            while (videoInputIter.hasNext()) {
+                Element videoInputElement = videoInputIter.next();
+
+                // 3. 提取 id 和 videoInputID
+                Integer id = Integer.parseInt(videoInputElement.elementText(QName.get("id", "http://www.isapi.org/ver20/XMLSchema")));
+                // 空标签(<videoInputID/>)会返回空字符串,需处理
+                String videoInputID = videoInputElement.elementText(QName.get("videoInputID", "http://www.isapi.org/ver20/XMLSchema")).trim();
+
+                // 4. 封装为 VideoInput 对象(可直接用 Map 替代,无需实体类)
+                VideoInput input = new VideoInput();
+                input.setId(id);
+                input.setVideoInputID(videoInputID.isEmpty() ? null : videoInputID);
+
+                videoInputList.add(input);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("XML解析失败:" + e.getMessage(), e);
+        }
+        return videoInputList;
+    }
+    @Data
+    public static class VideoInput {
+        private Integer id;
+        private String videoInputID;
+    }
 }

+ 1 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/hik/HikvisionProperties.java

@@ -44,6 +44,7 @@ public class HikvisionProperties {
             private String intelligenceAnalysis;
             private String systemStatus;
             private String deviceInfo;
+            private String sipInfo;
         }
         
         @Data

+ 56 - 55
yudao-server/src/main/resources/application-dev.yaml

@@ -315,59 +315,60 @@ sip:
   id: 34020000002000000001                  # 同上,另外增加编号,(可保持默认)
   password: 12345678
 
-  hikvision:
-    # ISAPI协议配置
-    isapi:
-      # 超脑设备配置
-      devices:
-        - id: "nvr-001"
-          name: "超脑巡检设备1"
-          ip: "172.26.0.52"
-          port: 80
-          username: "admin"
-          password: "rqny@szh2026"
-          protocol: "http"
-          timeout: 10000
-          # ISAPI接口路径
-          paths:
-            event-notification: "/ISAPI/Event/notification/"
-            intelligence-analysis: "/ISAPI/Intelligent/Analysis/"
-            system-status: "/ISAPI/System/status"
-            device-info: "/ISAPI/System/deviceInfo"
-          # 订阅配置
-          subscription:
-            heartbeat-interval: 30
-            event-types: "All"
-      # 回调配置
-      callback:
-        enabled: true
-        # 本地接收告警的接口地址(超脑会调用这个地址)
-        local-url: "http://172.26.0.3:8080/admin-api/hikvision/alarm/callback"
-        # 用于接收图片的接口
-        image-url: "http://172.26.0.3:8080/admin-api/hikvision/image/callback"
-        retry-times: 3
-        retry-interval: 1000
-      # 存储配置
-      storage:
-        save-alarm: true
-        save-image: false
-        image-path: "./alarm-images/"
+hikvision:
+  # ISAPI协议配置
+  isapi:
+    # 超脑设备配置
+    devices:
+      - id: "nvr-001"
+        name: "超脑巡检设备1"
+        ip: "172.26.0.52"
+        port: 80
+        username: "admin"
+        password: "rqny@szh2026"
+        protocol: "http"
+        timeout: 10000
+        # ISAPI接口路径
+        paths:
+          event-notification: "/ISAPI/Event/notification/"
+          intelligence-analysis: "/ISAPI/Intelligent/Analysis/"
+          system-status: "/ISAPI/System/status"
+          device-info: "/ISAPI/System/deviceInfo"
+          sip-info: "/ISAPI/System/Network/SIP/1/SIPInfo"
+        # 订阅配置
+        subscription:
+          heartbeat-interval: 30
+          event-types: "All"
+    # 回调配置
+    callback:
+      enabled: true
+      # 本地接收告警的接口地址(超脑会调用这个地址)
+      local-url: "http://172.26.0.3:8080/admin-api/hikvision/alarm/callback"
+      # 用于接收图片的接口
+      image-url: "http://172.26.0.3:8080/admin-api/hikvision/image/callback"
+      retry-times: 3
+      retry-interval: 1000
+    # 存储配置
+    storage:
+      save-alarm: true
+      save-image: false
+      image-path: "./alarm-images/"
 
-    # 告警事件处理配置
-    alarm:
-      event-types:
-        - name: "入侵检测"
-          code: "IntrusionDetection"
-          level: "HIGH"
-        - name: "人员聚集"
-          code: "CrowdDetection"
-          level: "MEDIUM"
-        - name: "安全帽检测"
-          code: "HelmetDetection"
-          level: "MEDIUM"
-        - name: "区域入侵"
-          code: "RegionIntrusion"
-          level: "HIGH"
-        - name: "设备离线"
-          code: "DeviceOffline"
-          level: "CRITICAL"
+  # 告警事件处理配置
+  alarm:
+    event-types:
+      - name: "入侵检测"
+        code: "IntrusionDetection"
+        level: "HIGH"
+      - name: "人员聚集"
+        code: "CrowdDetection"
+        level: "MEDIUM"
+      - name: "安全帽检测"
+        code: "HelmetDetection"
+        level: "MEDIUM"
+      - name: "区域入侵"
+        code: "RegionIntrusion"
+        level: "HIGH"
+      - name: "设备离线"
+        code: "DeviceOffline"
+        level: "CRITICAL"