| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- package com.telerobot.fs.utils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.w3c.dom.Document;
- import org.w3c.dom.Element;
- import org.w3c.dom.Node;
- import org.w3c.dom.NodeList;
- import javax.xml.parsers.DocumentBuilder;
- import javax.xml.parsers.DocumentBuilderFactory;
- import java.io.ByteArrayInputStream;
- import java.nio.charset.StandardCharsets;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- public class XmlUtils {
- protected final static Logger logger = LoggerFactory.getLogger(XmlUtils.class);
- /** 从 contact 标签中提取 sip:user@host:port 的 host:port */
- private static final Pattern CONTACT_HOST_PORT = Pattern.compile(
- "sip:[^@;>\\s]+@([^:;>\\s]+):(\\d+)", Pattern.CASE_INSENSITIVE);
- /**
- * 从 sofia xmlstatus profile X reg 的 XML 中解析已注册分机的 Contact(ip:port)。
- * 匹配规则与 GUI FsConfController.convertProfileRegExtnumXml 对齐:
- * 1) sip-auth-user 等于目标分机;或 2) user(@ 前部分)等于目标分机。
- * 地址优先取 network-ip:network-port(与 GUI 展示一致,为 NAT 后公网地址);
- * 若缺失再从 contact 的 sip URI 回退解析。
- * 注意:反向注册场景 ESL 事件常见 username=unknown,sip-auth-user 可能为空/unknown,
- * 仅匹配 sip-auth-user 会导致漏找(GUI 能显示而 callcenter 解析不到)。
- *
- * @param xml sofia xmlstatus 响应全文
- * @param extensionNumber 网关 authUsername / 分机号
- * @return ip:port,未找到返回空串
- */
- public static String parseFsOnlineUserListXml(String xml, String extensionNumber) {
- if (StringUtils.isNullOrEmpty(xml) || StringUtils.isNullOrEmpty(extensionNumber)) {
- return "";
- }
- String target = extensionNumber.trim();
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document doc = builder.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
- NodeList registrations = doc.getElementsByTagName("registration");
- int regCount = registrations.getLength();
- logger.info("parseFsOnlineUserListXml: registration条数={}, 目标分机={}", regCount, target);
- for (int i = 0; i < regCount; i++) {
- Element registration = (Element) registrations.item(i);
- String sipAuthUser = getChildText(registration, "sip-auth-user");
- String userRaw = getChildText(registration, "user");
- String userPart = extractUserPart(userRaw);
- boolean matchedByAuth = target.equals(sipAuthUser);
- boolean matchedByUser = target.equals(userPart);
- if (!matchedByAuth && !matchedByUser) {
- continue;
- }
- String matchBy = matchedByAuth ? "sip-auth-user" : "user";
- String networkIp = getChildText(registration, "network-ip");
- String networkPort = getChildText(registration, "network-port");
- String contactTag = getChildText(registration, "contact");
- String contact;
- if (!StringUtils.isNullOrEmpty(networkIp) && !StringUtils.isNullOrEmpty(networkPort)) {
- contact = networkIp + ":" + networkPort;
- } else {
- contact = extractHostPortFromContact(contactTag);
- logger.info("parseFsOnlineUserListXml: network-ip/port 缺失,回退解析 contact 标签,matchBy={}, contact={}", matchBy, contact);
- }
- if (StringUtils.isNullOrEmpty(contact)) {
- logger.warn("parseFsOnlineUserListXml: 已匹配 registration 但 Contact 为空,matchBy={}, user={}, sip-auth-user={}, contactTag={}", matchBy, userRaw, sipAuthUser, contactTag);
- continue;
- }
- logger.info("parseFsOnlineUserListXml: 匹配成功 index={}, matchBy={}, user={}, sip-auth-user={}, contact={}", i, matchBy, userRaw, sipAuthUser, contact);
- return contact;
- }
- // 未命中时输出少量候选,便于对比 GUI「查看状态」
- int sample = Math.min(regCount, 5);
- for (int i = 0; i < sample; i++) {
- Element registration = (Element) registrations.item(i);
- logger.info("parseFsOnlineUserListXml: 未命中样例[{}] user={}, sip-auth-user={}, network={}:{}",
- i,
- getChildText(registration, "user"),
- getChildText(registration, "sip-auth-user"),
- getChildText(registration, "network-ip"),
- getChildText(registration, "network-port"));
- }
- } catch (Throwable e) {
- logger.error("parseFsOnlineUserListXml error! {} {}", e.toString(),
- CommonUtils.getStackTraceString(e.getStackTrace()));
- // DOM 失败时(contact 含未转义 <> 等)用正则兜底,对齐 GUI 的 user + network-ip
- String fallback = parseFsOnlineUserListXmlByRegex(xml, target);
- if (!StringUtils.isNullOrEmpty(fallback)) {
- return fallback;
- }
- }
- return "";
- }
- /**
- * DOM 解析失败时的正则兜底:按 <user>分机@...</user> 定位 registration 片段,再取 network-ip/port。
- */
- private static String parseFsOnlineUserListXmlByRegex(String xml, String target) {
- try {
- Pattern regBlock = Pattern.compile("<registration>([\\s\\S]*?)</registration>", Pattern.CASE_INSENSITIVE);
- Matcher m = regBlock.matcher(xml);
- int idx = 0;
- while (m.find()) {
- String block = m.group(1);
- String sipAuthUser = extractXmlTag(block, "sip-auth-user");
- String userRaw = extractXmlTag(block, "user");
- String userPart = extractUserPart(userRaw);
- boolean matched = target.equals(sipAuthUser) || target.equals(userPart);
- if (!matched) {
- idx++;
- continue;
- }
- String networkIp = extractXmlTag(block, "network-ip");
- String networkPort = extractXmlTag(block, "network-port");
- String contact;
- if (!StringUtils.isNullOrEmpty(networkIp) && !StringUtils.isNullOrEmpty(networkPort)) {
- contact = networkIp + ":" + networkPort;
- } else {
- contact = extractHostPortFromContact(extractXmlTag(block, "contact"));
- }
- if (!StringUtils.isNullOrEmpty(contact)) {
- logger.info("parseFsOnlineUserListXml: 正则兜底匹配成功 index={}, user={}, sip-auth-user={}, contact={}", idx, userRaw, sipAuthUser, contact);
- return contact;
- }
- idx++;
- }
- } catch (Throwable e) {
- logger.error("parseFsOnlineUserListXmlByRegex error! {}", e.toString());
- }
- return "";
- }
- private static String getChildText(Element parent, String tagName) {
- NodeList list = parent.getElementsByTagName(tagName);
- if (list == null || list.getLength() == 0) {
- return "";
- }
- Node node = list.item(0);
- if (node == null || node.getTextContent() == null) {
- return "";
- }
- return node.getTextContent().trim();
- }
- private static String extractUserPart(String userRaw) {
- if (StringUtils.isNullOrEmpty(userRaw)) {
- return "";
- }
- int at = userRaw.indexOf('@');
- if (at > 0) {
- return userRaw.substring(0, at).trim();
- }
- return userRaw.trim();
- }
- private static String extractHostPortFromContact(String contactTag) {
- if (StringUtils.isNullOrEmpty(contactTag)) {
- return "";
- }
- Matcher matcher = CONTACT_HOST_PORT.matcher(contactTag);
- if (matcher.find()) {
- return matcher.group(1) + ":" + matcher.group(2);
- }
- return "";
- }
- private static String extractXmlTag(String block, String tagName) {
- Pattern p = Pattern.compile("<" + tagName + ">\\s*([^<]*?)\\s*</" + tagName + ">",
- Pattern.CASE_INSENSITIVE);
- Matcher m = p.matcher(block);
- if (m.find()) {
- return m.group(1).trim();
- }
- return "";
- }
- }
|