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/BtsTcpTransport.java | 97 ++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 89 insertions(+), 8 deletions(-)
diff --git a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsTcpTransport.java b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsTcpTransport.java
index 8c02695..fa9eb5e 100644
--- a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsTcpTransport.java
+++ b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsTcpTransport.java
@@ -1,21 +1,27 @@
package com.ruoyi.device.bts;
import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;
-import com.ruoyi.device.api.DeviceAdapterException;
public final class BtsTcpTransport implements BtsTransport
{
private static final byte[] FRAME_END = "#\r\n".getBytes(StandardCharsets.UTF_8);
private static final int DEFAULT_MAX_RESPONSE_BYTES = 16 * 1024 * 1024;
+ private static final int MAX_RESPONSE_TIMEOUT_MS = 30_000;
private final String host;
private final int port;
@@ -39,6 +45,9 @@
this.maxResponseBytes = maxResponseBytes;
if (host.isBlank() || port < 1 || port > 65535) throw new IllegalArgumentException("Invalid BTS TCP endpoint");
if (connectTimeoutMs < 1 || responseTimeoutMs < 1) throw new IllegalArgumentException("Timeouts must be positive");
+ // 厂商协议声明单条命令最长不超过约 30 秒,避免串行请求队列被错误配置长期阻塞。
+ if (responseTimeoutMs > MAX_RESPONSE_TIMEOUT_MS)
+ throw new IllegalArgumentException("BTS response timeout must not exceed 30 seconds");
if (maxResponseBytes < 1) throw new IllegalArgumentException("maxResponseBytes must be positive");
}
@@ -56,10 +65,41 @@
output.flush();
return readFrame(socket.getInputStream());
}
+ catch (ConnectException ex)
+ {
+ closeSocket();
+ throw new BtsTransportException(BtsTransportException.Reason.CONNECTION_FAILED,
+ "BTS TCP connection failed for " + host + ":" + port, ex);
+ }
+ catch (SocketTimeoutException ex)
+ {
+ closeSocket();
+ throw new BtsTransportException(BtsTransportException.Reason.TIMEOUT,
+ "BTS TCP exchange timed out for " + host + ":" + port, ex);
+ }
+ catch (EOFException ex)
+ {
+ closeSocket();
+ throw new BtsTransportException(BtsTransportException.Reason.DISCONNECTED,
+ "BTS disconnected before completing a response for " + host + ":" + port, ex);
+ }
+ catch (ResponseTooLargeException ex)
+ {
+ closeSocket();
+ throw new BtsTransportException(BtsTransportException.Reason.RESPONSE_TOO_LARGE,
+ "BTS response exceeded " + maxResponseBytes + " bytes for " + host + ":" + port, ex);
+ }
+ catch (InvalidUtf8Exception ex)
+ {
+ closeSocket();
+ throw new BtsTransportException(BtsTransportException.Reason.INVALID_UTF8,
+ "BTS response is not valid UTF-8 for " + host + ":" + port, ex);
+ }
catch (IOException ex)
{
closeSocket();
- throw new DeviceAdapterException("BTS TCP exchange failed for " + host + ":" + port, ex);
+ throw new BtsTransportException(BtsTransportException.Reason.IO_FAILURE,
+ "BTS TCP exchange failed for " + host + ":" + port, ex);
}
finally
{
@@ -80,24 +120,50 @@
{
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int matched = 0;
- while (buffer.size() < maxResponseBytes)
+ while (true)
{
int value = input.read();
- if (value < 0) throw new IOException("BTS closed the connection before the response frame ended");
+ if (value < 0) throw new EOFException("BTS closed the connection before the response frame ended");
if ((byte) value == FRAME_END[matched])
{
matched++;
- if (matched == FRAME_END.length) return buffer.toString(StandardCharsets.UTF_8);
+ if (matched == FRAME_END.length) return decodeUtf8(buffer.toByteArray());
continue;
}
if (matched > 0)
{
- buffer.write(FRAME_END, 0, matched);
+ for (int i = 0; i < matched; i++) appendPayload(buffer, FRAME_END[i]);
matched = 0;
+ if ((byte) value == FRAME_END[0])
+ {
+ matched = 1;
+ continue;
+ }
}
- buffer.write(value);
+ appendPayload(buffer, value);
}
- throw new IOException("BTS response exceeded " + maxResponseBytes + " bytes");
+ }
+
+ private void appendPayload(ByteArrayOutputStream buffer, int value) throws ResponseTooLargeException
+ {
+ if (buffer.size() >= maxResponseBytes) throw new ResponseTooLargeException();
+ buffer.write(value);
+ }
+
+ private static String decodeUtf8(byte[] bytes) throws InvalidUtf8Exception
+ {
+ try
+ {
+ return StandardCharsets.UTF_8.newDecoder()
+ .onMalformedInput(CodingErrorAction.REPORT)
+ .onUnmappableCharacter(CodingErrorAction.REPORT)
+ .decode(ByteBuffer.wrap(bytes))
+ .toString();
+ }
+ catch (CharacterCodingException ex)
+ {
+ throw new InvalidUtf8Exception(ex);
+ }
}
@Override
@@ -129,4 +195,19 @@
socket = null;
}
}
+
+ private static final class ResponseTooLargeException extends IOException
+ {
+ private static final long serialVersionUID = 1L;
+ }
+
+ private static final class InvalidUtf8Exception extends IOException
+ {
+ private static final long serialVersionUID = 1L;
+
+ private InvalidUtf8Exception(Throwable cause)
+ {
+ super(cause);
+ }
+ }
}
--
Gitblit v1.7.1