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