From db2eb9412ddc4ff687eb4c9b862053f49f9aec7b Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期三, 26 八月 2026 13:16:42 +0800
Subject: [PATCH] feat(device): 完善 BTS 只读协议与模拟器
---
ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsXmlCodec.java | 96 +++++++++++++++++++++++++++++++++++++++++------
1 files changed, 83 insertions(+), 13 deletions(-)
diff --git a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsXmlCodec.java b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsXmlCodec.java
index 058135b..d0be888 100644
--- a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsXmlCodec.java
+++ b/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);
+ }
}
--
Gitblit v1.7.1