yeg
2026-08-26 db2eb9412ddc4ff687eb4c9b862053f49f9aec7b
ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsXmlCodec.java
@@ -14,6 +14,7 @@
import java.util.Map;
import java.util.Set;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -21,9 +22,11 @@
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.DefaultHandler;
import com.ruoyi.device.model.ChannelIdentity;
import com.ruoyi.device.model.DetectedChannel;
import com.ruoyi.device.model.DeviceWorkStatus;
import com.ruoyi.device.model.HistoryAvailability;
import com.ruoyi.device.model.HistoryPage;
import com.ruoyi.device.model.HistorySample;
import com.ruoyi.device.model.Measurement;
@@ -68,6 +71,24 @@
        return envelope(list.append("</list>").toString());
    }
    /**
     * 查询各通道当前 BTS 测试的历史数据上传进度。
     */
    public String inquireHistoryRequest(Set<ChannelIdentity> channels)
    {
        StringBuilder list = new StringBuilder("<cmd>inquiredf</cmd><list count=\"").append(channels.size()).append("\">");
        for (ChannelIdentity channel : channels)
        {
            requireOwned(channel);
            list.append("<chl devtype=\"").append(channel.devtype())
                    .append("\" devid=\"").append(channel.devid())
                    .append("\" subdevid=\"").append(channel.subdevid())
                    .append("\" chlid=\"").append(channel.chlid())
                    .append("\" testid=\"0\"/>");
        }
        return envelope(list.append("</list>").toString());
    }
    public String downloadRequest(ChannelIdentity channel, String btsTestId, long startPosition, int limit)
    {
        requireOwned(channel);
@@ -92,7 +113,7 @@
            ChannelIdentity identity = new ChannelIdentity(adapterCode, integerAttribute(element, "devtype"),
                    integerAttribute(element, "devid"), integerAttribute(element, "subdevid"), chlid);
            channels.add(new DetectedChannel(identity, requiredAttribute(element, "ip"),
                    Boolean.parseBoolean(element.getTextContent().trim())));
                    booleanText(element.getTextContent(), "available")));
        }
        return List.copyOf(channels);
    }
@@ -106,7 +127,9 @@
        {
            Element element = (Element) nodes.item(i);
            String[] device = requiredAttribute(element, "dev").split("-");
            if (device.length < 4) throw new BtsProtocolException("Invalid BTS dev identity: " + element.getAttribute("dev"));
            if (device.length < 4)
                throw new BtsProtocolException(BtsProtocolException.Reason.INVALID_VALUE,
                        "Invalid BTS dev identity: " + element.getAttribute("dev"));
            ChannelIdentity identity = new ChannelIdentity(adapterCode, integer(device[0], "devtype"), integer(device[1], "devid"),
                    integer(device[2], "subdevid"), integer(device[3], "chlid"));
            snapshots.put(identity, new RealtimeSnapshot(identity,
@@ -119,11 +142,38 @@
        return Map.copyOf(snapshots);
    }
    public HistoryPage parseHistory(String xml, ChannelIdentity channel, long requestedStart)
    /**
     * 将 BTS 历史清单映射为厂商无关的通道历史可用性。
     */
    public List<HistoryAvailability> parseHistoryAvailability(String xml)
    {
        Document document = parse(xml, "inquiredf_resp");
        NodeList nodes = document.getElementsByTagName("chl");
        List<HistoryAvailability> availability = new ArrayList<>(nodes.getLength());
        for (int i = 0; i < nodes.getLength(); i++)
        {
            Element element = (Element) nodes.item(i);
            ChannelIdentity channel = new ChannelIdentity(adapterCode, integerAttribute(element, "devtype"),
                    integerAttribute(element, "devid"), integerAttribute(element, "subdevid"),
                    integerAttribute(element, "chlid"));
            availability.add(new HistoryAvailability(channel, requiredAttribute(element, "testid"),
                    longAttribute(element, "count"), booleanText(element.getTextContent(), "uploadComplete")));
        }
        return List.copyOf(availability);
    }
    public HistoryPage parseHistory(String xml, ChannelIdentity channel, String requestedTestId, long requestedStart)
    {
        Document document = parse(xml, "download_resp");
        Element download = firstElement(document, "download");
        String btsTestId = requiredAttribute(download, "testid");
        if (integerAttribute(download, "devtype") != channel.devtype()
                || integerAttribute(download, "devid") != channel.devid()
                || integerAttribute(download, "subdevid") != channel.subdevid()
                || integerAttribute(download, "chlid") != channel.chlid()
                || !btsTestId.equals(requestedTestId))
            throw new BtsProtocolException(BtsProtocolException.Reason.IDENTITY_MISMATCH,
                    "BTS history response identity does not match the request");
        NodeList nodes = document.getElementsByTagName("data");
        List<HistorySample> samples = new ArrayList<>(nodes.getLength());
        long next = requestedStart;
@@ -153,10 +203,13 @@
            factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
            factory.setXIncludeAware(false);
            factory.setExpandEntityReferences(false);
            Document document = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
            DocumentBuilder builder = factory.newDocumentBuilder();
            builder.setErrorHandler(new DefaultHandler());
            Document document = builder.parse(new InputSource(new StringReader(xml)));
            String command = firstElement(document, "cmd").getTextContent().trim();
            if (!expectedCommand.equals(command))
                throw new BtsProtocolException("Expected BTS command " + expectedCommand + " but received " + command);
                throw new BtsProtocolException(BtsProtocolException.Reason.UNEXPECTED_COMMAND,
                        "Expected BTS command " + expectedCommand + " but received " + command);
            return document;
        }
        catch (BtsProtocolException ex)
@@ -165,7 +218,8 @@
        }
        catch (Exception ex)
        {
            throw new BtsProtocolException("Invalid BTS XML response", ex);
            throw new BtsProtocolException(BtsProtocolException.Reason.MALFORMED_XML,
                    "Invalid BTS XML response", ex);
        }
    }
@@ -201,7 +255,8 @@
        }
        catch (DateTimeParseException ex)
        {
            throw new BtsProtocolException("Invalid BTS device time: " + value, ex);
            throw new BtsProtocolException(BtsProtocolException.Reason.INVALID_VALUE,
                    "Invalid BTS device time: " + value, ex);
        }
    }
@@ -223,14 +278,16 @@
    private static Element firstElement(Document document, String tag)
    {
        NodeList elements = document.getElementsByTagName(tag);
        if (elements.getLength() == 0) throw new BtsProtocolException("Missing BTS element: " + tag);
        if (elements.getLength() == 0)
            throw new BtsProtocolException(BtsProtocolException.Reason.MISSING_FIELD, "Missing BTS element: " + tag);
        return (Element) elements.item(0);
    }
    private static String requiredAttribute(Element element, String name)
    {
        String value = element.getAttribute(name);
        if (value == null || value.isBlank()) throw new BtsProtocolException("Missing BTS attribute: " + name);
        if (value == null || value.isBlank())
            throw new BtsProtocolException(BtsProtocolException.Reason.MISSING_FIELD, "Missing BTS attribute: " + name);
        return value.trim();
    }
@@ -253,7 +310,8 @@
        }
        catch (NumberFormatException ex)
        {
            throw new BtsProtocolException("Invalid integer BTS attribute: " + name, ex);
            throw new BtsProtocolException(BtsProtocolException.Reason.INVALID_VALUE,
                    "Invalid integer BTS attribute: " + name, ex);
        }
    }
@@ -273,7 +331,8 @@
        }
        catch (NumberFormatException ex)
        {
            throw new BtsProtocolException("Invalid integer BTS attribute: " + name, ex);
            throw new BtsProtocolException(BtsProtocolException.Reason.INVALID_VALUE,
                    "Invalid integer BTS attribute: " + name, ex);
        }
    }
@@ -285,7 +344,8 @@
        }
        catch (NumberFormatException ex)
        {
            throw new BtsProtocolException("Invalid integer BTS attribute: " + name, ex);
            throw new BtsProtocolException(BtsProtocolException.Reason.INVALID_VALUE,
                    "Invalid integer BTS attribute: " + name, ex);
        }
    }
@@ -303,7 +363,17 @@
        }
        catch (NumberFormatException ex)
        {
            throw new BtsProtocolException("Invalid decimal BTS attribute: " + name, ex);
            throw new BtsProtocolException(BtsProtocolException.Reason.INVALID_VALUE,
                    "Invalid decimal BTS attribute: " + name, ex);
        }
    }
    private static boolean booleanText(String value, String name)
    {
        String normalized = value == null ? "" : value.trim();
        if ("true".equalsIgnoreCase(normalized)) return true;
        if ("false".equalsIgnoreCase(normalized)) return false;
        throw new BtsProtocolException(BtsProtocolException.Reason.INVALID_VALUE,
                "Invalid boolean BTS value: " + name);
    }
}