Onvif WS-BaseNotification事件通知解析与反序列化问题咨询
我之前也踩过Onvif事件处理的坑,WS-BaseNotification的通用框架和Onvif的自定义事件扩展确实容易让人混淆,咱们一步步拆解解决你的问题:
1. 先理清核心逻辑:WS-BaseNotification是壳,Onvif事件是内容
WS-BaseNotification定义了通用的通知结构(就是你拿到的NotificationMessageHolderType),而Onvif在这个框架上做了定制:
Topic元素是事件的"分类标签",用来区分不同类型的事件(比如你的tns1:AccessPoint/State/Enabled就是接入点状态变化事件)Message里的Source、Key、Data才是Onvif规范定义的事件核心内容,对应你提到的"谁产生的"、"何时发生"、"发生了什么"
你已经成功把通知反序列化成了org.onvif.ver10.schema.Message,这一步是对的,接下来就是把这个通用的Message对象,根据Topic映射到你自己的事件实现类(比如AccessPointState)。
2. 修正你的反序列化与映射代码
你不需要手动调用JAXB去反序列化Node,因为NotificationMessageHolderType的getMessage()方法已经返回了现成的org.onvif.ver10.schema.Message对象。你要做的是根据Topic的值,把Message里的Source和Data提取出来,填充到自定义事件类中。
给你调整后的代码示例:
PullMessagesResponse pullMessagesResponse = pullPointSubscription.pullMessages(pullMessages); List<NotificationMessageHolderType> notificationMessages = pullMessagesResponse.getNotificationMessage(); for (NotificationMessageHolderType msgHolder : notificationMessages) { // 1. 获取事件主题,判断事件类型 TopicExpressionType topic = msgHolder.getTopic(); String topicValue = topic.getValue(); // 拿到 "tns1:AccessPoint/State/Enabled" // 2. 获取Onvif标准的Message对象 org.onvif.ver10.schema.Message onvifMessage = msgHolder.getMessage(); // 3. 根据主题映射到自定义事件类 if ("tns1:AccessPoint/State/Enabled".equals(topicValue)) { AccessPointState accessPointEvent = new AccessPointState(); // 填充Source中的设备标识信息 if (onvifMessage.getSource() != null) { for (Item item : onvifMessage.getSource().getSimpleItemOrElementItem()) { if (item instanceof SimpleItem) { SimpleItem simpleItem = (SimpleItem) item; switch (simpleItem.getName()) { case "AccessPointToken": accessPointEvent.setAccessPointToken(simpleItem.getValue()); break; case "Device Source": accessPointEvent.setDeviceSource(simpleItem.getValue()); break; } } } } // 填充Data中的事件详情 if (onvifMessage.getData() != null) { for (Item item : onvifMessage.getData().getSimpleItemOrElementItem()) { if (item instanceof SimpleItem) { SimpleItem simpleItem = (SimpleItem) item; if ("State".equals(simpleItem.getName())) { // 这里根据实际类型转换,比如State是int或boolean accessPointEvent.setState(Integer.parseInt(simpleItem.getValue())); } } } } // 处理你的自定义事件 handleAccessPointEvent(accessPointEvent); } // 其他主题的事件处理逻辑可以继续添加... }
3. 利用GetEventProperties实现动态映射(进阶方案)
你提到的GetEventPropertiesResponse是关键!这个接口会返回设备实际支持的所有主题,以及每个主题对应的元数据(比如Data项的名称、类型、可选性)。用它可以实现更灵活的事件映射,不用硬编码主题值:
步骤说明:
- 获取设备的事件属性:
GetEventPropertiesResponse eventProperties = onvifDevice.getEventPort().getEventProperties();
- 解析主题元数据:
遍历eventProperties.getTopicSet().getTopic()和eventProperties.getTopicComplexType(),可以拿到每个主题的路径、对应的Data结构(比如哪些SimpleItem是必须的,类型是什么)。 - 动态生成事件对象或构建映射关系:
- 如果是固定设备,可以提前根据这些元数据编写对应的事件类(比如
AccessPointState) - 如果要适配多设备,可以用反射(比如Apache Commons BeanUtils)或者动态代理,在运行时根据元数据创建对象并填充属性,避免硬编码每个主题的处理逻辑。
- 如果是固定设备,可以提前根据这些元数据编写对应的事件类(比如
4. 关于Schema的补充说明
你找不到的主题结构描述,其实是设备厂商基于Onvif标准扩展的内容,不在通用的Onvif WSDL里。Onvif的核心规范里只定义了事件的通用结构,而设备特定的主题和数据结构,必须通过GetEventProperties接口获取——这也是Onvif规范要求设备必须实现的接口。
内容的提问来源于stack exchange,提问作者Manticore




