Quellcode durchsuchen

refactor(telemetry): 优化协议工具类地址解析逻辑

- 添加Logger用于日志记录
- 实现extractAddrInfo方法动态提取地址信息
- 实现calculateAddrByteLength方法计算地址字节长度
- 重构buildShutdownAckPacket方法使用动态地址解析
- 重构buildCustomReplyFrame方法统一地址处理逻辑
- 重构buildEndReplyFrame方法完善地址交换处理
- 移除buildErrorResponse废弃方法

feat(environment): 增强实时数据处理与报警功能

- 添加AlarmUtil依赖注入
- 实现按节点ID的数据处理逻辑和系数转换
- 添加数据精度控制保留两位小数
- 实现温度和湿度报警检查机制
- 集成报警数据存储和短信通知功能
- 添加未知节点ID的异常处理

feat(pipe-network): 扩展安全配置和API文档

- 在SecurityConfig中添加可视化接口免认证访问
- 为ServerController添加Swagger API注解
- 配置服务器监控接口的API文档支持
林仔 vor 1 Monat
Ursprung
Commit
979023e810

+ 75 - 8
environment-service/src/main/java/com/zksy/environment/config/RSServerService.java

@@ -11,6 +11,7 @@ import rk.netDevice.sdk.p2.*;
 
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
+import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
 import java.util.List;
@@ -40,6 +41,8 @@ public class RSServerService {
     private SmsUtil smsUtil;
     @Autowired
     private DevicePhoneFetchUtil devicePhoneFetchUtil;
+    @Autowired
+    private com.zksy.environment.utils.AlarmUtil alarmUtil;
 
     @PostConstruct
     public void init() {
@@ -109,27 +112,91 @@ public class RSServerService {
 
                 @Override
                 public void receiveRealtimeData(RealTimeData data) {
-
                     for (NodeData nd : data.getNodeList()) {
                         try {
+                            // 获取当前节点ID
+                            Integer nodeId = nd.getNodeId();
+                            // 定义最终要存储的数值
+                            float finalValue;
+                            // 字段赋值标记:true=存floatValue(悬浮物),false=存hum(湿度)
+                            boolean saveAsFloatValue = false;
+
+                            // ====================== 按节点处理数值 ======================
+                            if (nodeId == 1) {
+                                // 节点1:悬浮物,系数10
+                                finalValue = nd.getFloatValue() * 10;
+                                saveAsFloatValue = true;
+                            } else if (nodeId == 2) {
+                                // 节点2:湿度,系数1
+                                finalValue = nd.getHum() * 1;
+                            } else if (nodeId == 3) {
+                                // 节点3:湿度,系数0.1
+                                finalValue = nd.getHum() * 0.1f;
+                            } else if (nodeId == 4) {
+                                // 节点4:湿度,系数10
+                                finalValue = nd.getHum() * 10;
+                            } else if (nodeId == 5) {
+                                // 节点5:湿度,系数0.1
+                                finalValue = nd.getHum() * 0.1f;
+                            } else {
+                                // 未知节点,跳过处理
+                                log.warn("未知节点ID:{},不进行数据处理", nodeId);
+                                continue;
+                            }
+
+                            // 保留两位小数(核心处理)
+                            finalValue = Math.round(finalValue * 100) / 100.0f;
+
+                            // ====================== 封装实体 ======================
                             ERealTimeData realTimeData = new ERealTimeData();
                             realTimeData.setDeviceId(data.getDeviceId());
-                            realTimeData.setNodeId(nd.getNodeId());
+                            realTimeData.setNodeId(nodeId);
                             realTimeData.setTem(nd.getTem());
-                            realTimeData.setHum(nd.getHum());
                             realTimeData.setLng(nd.getLng());
                             realTimeData.setLat(nd.getLat());
                             realTimeData.setCoordinateType(String.valueOf(data.getCoordinateType()));
                             realTimeData.setRelayStatus(String.valueOf(data.getRelayStatus()));
-                            realTimeData.setFloatValue(String.valueOf(nd.getFloatValue()));
                             realTimeData.setCreateTime(LocalDateTime.now());
-                            realTimeDataMapper.insert(realTimeData);
 
-                            //获取能够发送的手机号
-                            List<String> devicePhoneList = devicePhoneFetchUtil.getPhoneListByDeviceId(String.valueOf(data.getDeviceId()));
+                            // 按节点类型赋值对应字段
+                            if (saveAsFloatValue) {
+                                // 节点1:存入悬浮物
+                                realTimeData.setFloatValue(String.valueOf(finalValue));
+                                // 节点1不需要湿度,清空避免脏数据
+                                realTimeData.setHum(0.0f);
+                            } else {
+                                // 节点2-5:存入湿度
+                                realTimeData.setHum(finalValue);
+                                // 非节点1清空浮点值
+                                realTimeData.setFloatValue("0.00");
+                            }
+
+                            // 数据库插入
+                            realTimeDataMapper.insert(realTimeData);
 
-                            //传入手机号参数
+                            // ====================== 报警处理 ======================
+                            String deviceId = String.valueOf(data.getDeviceId());
+                            String deviceName = "环境设备";
+                            String deviceType = "environment";
+
+                            // 温度判断(
+                            if (!Float.isNaN(nd.getTem())) {
+                                alarmUtil.checkAndSaveAlarm(deviceName, deviceId, deviceType,
+                                        "温度预警", "WARN-TEMPERATURE", 40.0,
+                                        BigDecimal.valueOf(nd.getTem()), "环境温度报警");
+                            }
+
+                            // 只有非节点1才判断湿度报警(节点1无湿度数据)
+                            if (nodeId != 1 && !Float.isNaN(nd.getHum())) {
+                                alarmUtil.checkAndSaveAlarm(deviceName, deviceId, deviceType,
+                                        "湿度预警", "WARN-HUMIDITY", 90.0,
+                                        BigDecimal.valueOf(finalValue), "环境湿度报警");
+                            }
+
+                            // 获取手机号并发送短信
+                            List<String> devicePhoneList = devicePhoneFetchUtil.getPhoneListByDeviceId(deviceId);
                             smsUtil.checkDeviceAlarmAndSend(data, nd, devicePhoneList);
+
                         } catch (Exception e) {
                             log.error("实时数据入库失败:设备ID={}, 节点ID={}", data.getDeviceId(), nd.getNodeId(), e);
                         }

+ 4 - 0
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/monitor/ServerController.java

@@ -1,5 +1,7 @@
 package com.zksy.web.controller.monitor;
 
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -14,10 +16,12 @@ import com.zksy.framework.web.domain.Server;
  */
 @RestController
 @RequestMapping("/monitor/server")
+@Api(tags = "服务器监控")
 public class ServerController
 {
     @PreAuthorize("@ss.hasPermi('monitor:server:list')")
     @GetMapping()
+    @ApiOperation("服务器信息")
     public AjaxResult getInfo() throws Exception
     {
         Server server = new Server();

+ 1 - 1
pipe-network-service/zksy-framework/src/main/java/com/zksy/framework/config/SecurityConfig.java

@@ -111,7 +111,7 @@ public class SecurityConfig
             .authorizeHttpRequests((requests) -> {
                 permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
                 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-                requests.antMatchers("/login", "/register", "/captchaImage").permitAll()
+                requests.antMatchers("/login", "/register", "/captchaImage","/**/visualization/**").permitAll()
                     // 静态资源,可匿名访问
                     .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                     .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()

+ 181 - 125
telemetry-service/src/main/java/com/zksy/telemetry/utils/ProtocolUtils.java

@@ -6,8 +6,10 @@ import java.time.Duration;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.util.Arrays;
+import java.util.logging.Logger;
 
 public class ProtocolUtils {
+    private static final Logger logger = Logger.getLogger(ProtocolUtils.class.getName());
     /**
      * 将2000年起始的时间戳转换为LocalDateTime
      */
@@ -116,23 +118,24 @@ public class ProtocolUtils {
      * 12 34 56 00 16 80 34 0B 13 81 23 45 67 80 0B 00 00 00 00 00 10 D1
      */
     public static byte[] buildShutdownAckPacket(byte[] originalFrame) {
-        // 提取源、目的地址
-        int srcLenIndex = 7;
-        int srcLen = originalFrame[srcLenIndex] & 0xFF;
-        int srcByteLen = (srcLen + 1) / 2;
-        int srcStart = srcLenIndex + 1;
-        int srcEnd = srcStart + srcByteLen;
-
-        int dstLenIndex = srcEnd;
-        int dstLen = originalFrame[dstLenIndex] & 0xFF;
-        int dstByteLen = (dstLen + 1) / 2;
-        int dstStart = dstLenIndex + 1;
-        int dstEnd = dstStart + dstByteLen;
-
-        // 结束通讯帧包序号固定 0x80
-        byte packetSeq = (byte) 0x80;
-
-        byte[] response = new byte[3 + 2 + 1 + 1 + 1 + dstByteLen + 1 + srcByteLen + 1];
+        // 步骤1:动态提取地址信息(对齐validateMessage)
+        int[] addrInfo = extractAddrInfo(originalFrame);
+        int srcLenFlag = addrInfo[0];
+        int srcAddrByteLen = addrInfo[1];
+        int srcAddrStart = addrInfo[2];
+        int srcAddrEnd = addrInfo[3];
+        int destLenFlag = addrInfo[4];
+        int destAddrByteLen = addrInfo[5];
+        int destAddrStart = addrInfo[6];
+        int destAddrEnd = addrInfo[7];
+
+        // 步骤2:提取地址字节
+        byte[] srcAddr = Arrays.copyOfRange(originalFrame, srcAddrStart, srcAddrEnd);
+        byte[] destAddr = Arrays.copyOfRange(originalFrame, destAddrStart, destAddrEnd);
+
+        // 步骤3:构造应答帧
+        int totalLen = 3 + 2 + 1 + 1 + 1 + destAddrByteLen + 1 + srcAddrByteLen + 1;
+        byte[] response = new byte[totalLen];
         int idx = 0;
 
         // 系统识别码
@@ -141,25 +144,22 @@ public class ProtocolUtils {
         response[idx++] = 0x56;
 
         // 长度
-        int totalLen = response.length;
         response[idx++] = (byte) ((totalLen >> 8) & 0xFF);
         response[idx++] = (byte) (totalLen & 0xFF);
 
-        // 包序号
-        response[idx++] = packetSeq;
-
-        // 帧类型 0x34
+        // 包序号0x80 + 帧类型0x34
+        response[idx++] = (byte) 0x80;
         response[idx++] = 0x34;
 
-        // 源地址 = 目的地址
-        response[idx++] = (byte) dstLen;
-        System.arraycopy(originalFrame, dstStart, response, idx, dstByteLen);
-        idx += dstByteLen;
+        // 源地址=原目的地址(动态)
+        response[idx++] = (byte) destLenFlag;
+        System.arraycopy(destAddr, 0, response, idx, destAddrByteLen);
+        idx += destAddrByteLen;
 
-        // 目的地址 = 源地址
-        response[idx++] = (byte) srcLen;
-        System.arraycopy(originalFrame, srcStart, response, idx, srcByteLen);
-        idx += srcByteLen;
+        // 目的地址=原源地址(动态)
+        response[idx++] = (byte) srcLenFlag;
+        System.arraycopy(srcAddr, 0, response, idx, srcAddrByteLen);
+        idx += srcAddrByteLen;
 
         // 异或校验
         byte xor = calculateXorCheck(Arrays.copyOfRange(response, 0, idx));
@@ -167,89 +167,77 @@ public class ProtocolUtils {
 
         return response;
     }
-
-    public static byte[] buildErrorResponse(String msg) {
-        byte[] text = msg.getBytes();
-        byte[] data = new byte[3 + 2 + 1 + text.length + 1];
-        int idx = 0;
-        data[idx++] = 0x12;
-        data[idx++] = 0x34;
-        data[idx++] = 0x56;
-
-        int len = data.length;
-        data[idx++] = (byte) ((len >> 8) & 0xFF);
-        data[idx++] = (byte) (len & 0xFF);
-
-        data[idx++] = 0x7F; // 错误类型标识
-        System.arraycopy(text, 0, data, idx, text.length);
-        idx += text.length;
-
-        data[idx++] = calculateXorCheck(Arrays.copyOfRange(data, 0, idx - 1));
-        return data;
-    }
     public static byte[] buildCustomReplyFrame(byte[] request) throws IOException {
-        // 校验长度至少20个字节
-        if (request.length < 20) {
-            throw new IllegalArgumentException("请求帧长度不足,无法构造应答");
-        }
-
-        // 提取关键字段
-        byte[] header = Arrays.copyOfRange(request, 0, 4); // 系统识别码
-        byte packetSeq = request[5];                       // 包序号
-        byte frameType = request[6];                       // 帧类型(0x31)
-        byte addrLen = request[7];                         // 源地址长度(0B)
-        int srcAddrStart = 8;
-        int srcAddrEnd = srcAddrStart + 6;                 // 实际取6字节源地址
-        int destAddrStart = srcAddrEnd + 1;                // 跳过目的地址长度0B
-        int destAddrEnd = destAddrStart + 6;
-
+        // 步骤1:动态提取地址信息(对齐validateMessage)
+        int[] addrInfo = extractAddrInfo(request);
+        int srcLenFlag = addrInfo[0];
+        int srcAddrByteLen = addrInfo[1]; // 原源地址长度 m
+        int srcAddrStart = addrInfo[2];
+        int srcAddrEnd = addrInfo[3];
+        int destLenFlag = addrInfo[4];
+        int destAddrByteLen = addrInfo[5]; // 原目的地址长度 n
+        int destAddrStart = addrInfo[6];
+        int destAddrEnd = addrInfo[7];
+
+        // 步骤2:提取地址字节
         byte[] srcAddr = Arrays.copyOfRange(request, srcAddrStart, srcAddrEnd);
         byte[] destAddr = Arrays.copyOfRange(request, destAddrStart, destAddrEnd);
 
-        // --- 构造应答帧体 ---
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        // 步骤3:固定字段提取
+        byte[] sysId = Arrays.copyOfRange(request, 0, 3); // 系统识别码(3字节)
+        byte packetSeq = request[5]; // 包序号(1字节)
+        byte frameType = request[6]; // 帧类型0x31(1字节)
 
-        // 前四字节
-        bos.write(header, 0, 4);
-        // 帧长度 0x16
-        bos.write(new byte[]{0x16});
-        // 包序号、帧类型
-        bos.write(packetSeq);
-        bos.write(frameType);
-        bos.write(addrLen);
-
-        // 交换地址段:目的 → 源
-        bos.write(destAddr);   // 目的地址放前
-        bos.write(addrLen);
-        bos.write(srcAddr);    // 源地址放后
-
-        // 计算异或校验码
-        byte[] withoutCheck = bos.toByteArray();
-        byte xor = 0x00;
-        for (byte b : withoutCheck) xor ^= b;
-        bos.write(xor);
+        // 帧长度字段值 = 系统识别码(3字节)+2(自身) + 1(包序号) + 1(帧类型) + 1(源地址标识) + n(源地址) + 1(目的地址标识) + m(目的地址)
+        int frameLenValue = 3 + 2 + 1 + 1 + 1 + destAddrByteLen + 1 + srcAddrByteLen+1;
 
-        byte[] result = bos.toByteArray();
-        return result;
+        // 步骤4:构造帧体(直接写入正确长度,无需占位回填)
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        bos.write(sysId); // 系统识别码(3字节)
+        // 写入帧长度字段(包含自身2字节,高字节在前)
+        bos.write((byte) ((frameLenValue >> 8) & 0xFF));
+        bos.write((byte) (frameLenValue & 0xFF));
+
+        // 步骤5:地址交换(动态长度)
+        bos.write(packetSeq);          // 包序号
+        bos.write(frameType);          // 帧类型
+        bos.write(destLenFlag);        // 应答源地址标识=原目的标识
+        bos.write(destAddr);           // 应答源地址=原目的地址(n字节)
+        bos.write(srcLenFlag);         // 应答目的地址标识=原源标识
+        bos.write(srcAddr);            // 应答目的地址=原源地址(m字节)
+
+        // 步骤6:计算异或校验(仅计算不含校验位的帧体)
+        byte[] tempFrame = bos.toByteArray();
+        byte xor = calculateXorCheck(tempFrame);
+
+        // 步骤7:拼接最终帧(帧体 + 异或校验位)
+        ByteArrayOutputStream finalBos = new ByteArrayOutputStream();
+        finalBos.write(tempFrame);
+        finalBos.write(xor);
+
+        return finalBos.toByteArray();
     }
     public static byte[] buildEndReplyFrame(byte[] request) throws IOException {
-        if (request == null || request.length < 25) {
-            throw new IllegalArgumentException("请求帧长度不足,无法构造结束应答");
-        }
-
-        // 1 系统识别码
-        byte[] sysId = Arrays.copyOfRange(request, 0, 3); // 12 34 56
-
-        // 2 包序号
-        byte packetSeq = request[5]; // 81
+        // 步骤1:动态提取地址信息(对齐validateMessage)
+        int[] addrInfo = extractAddrInfo(request);
+        int srcLenFlag = addrInfo[0];
+        int srcAddrByteLen = addrInfo[1]; // 源地址长度 n
+        int srcAddrStart = addrInfo[2];
+        int srcAddrEnd = addrInfo[3];
+        int destLenFlag = addrInfo[4];
+        int destAddrByteLen = addrInfo[5]; // 目的地址长度 m
+        int destAddrStart = addrInfo[6];
+        int destAddrEnd = addrInfo[7];
+
+        // 步骤2:提取地址字节
+        byte[] srcAddr = Arrays.copyOfRange(request, srcAddrStart, srcAddrEnd);
+        byte[] destAddr = Arrays.copyOfRange(request, destAddrStart, destAddrEnd);
 
-        // 3 源/目的地址
-        byte srcLen = request[7];
-        byte[] srcAddr = Arrays.copyOfRange(request, 8, 14);
-        byte destLen = request[14];
-        byte[] destAddr = Arrays.copyOfRange(request, 15, 21);
+        // 步骤3:固定字段提取
+        byte[] sysId = Arrays.copyOfRange(request, 0, 3); // 系统识别码(3字节)
+        byte packetSeq = request[5]; // 包序号(1字节)
 
-        // 4 当前时间转换为从 2000-01-01 开始的秒数
+        // 步骤4:时间戳(4字节)
         long secondsSince2000 = getSecondsSince2000();
         byte[] timestamp = new byte[4];
         timestamp[0] = (byte) ((secondsSince2000 >> 24) & 0xFF);
@@ -257,37 +245,37 @@ public class ProtocolUtils {
         timestamp[2] = (byte) ((secondsSince2000 >> 8) & 0xFF);
         timestamp[3] = (byte) (secondsSince2000 & 0xFF);
 
-        // 5 构造内容区(从包序号开始,到时间戳结束)
+        // ========== 核心修正:计算包含自身2字节的帧长度字段值 ==========
+        // 帧长度字段值 = 2(自身) + 1(包序号) + 1(帧类型) + 1(源地址标识) + n(源地址) + 1(目的地址标识) + m(目的地址) + 4(时间戳)
+        int frameLenValue =3+ 2 + 1 + 1 + 1 + srcAddrByteLen + 1 + destAddrByteLen + 4+1;
+        // 拆分高低字节
+        byte lenHigh = (byte) ((frameLenValue >> 8) & 0xFF);
+        byte lenLow = (byte) (frameLenValue & 0xFF);
+
+        // 步骤5:构造内容区(包序号到时间戳,不含长度字段)
         ByteArrayOutputStream content = new ByteArrayOutputStream();
-        content.write(packetSeq);  // 包序号
-        content.write(0x34);       // 帧类型(替换成 0x34)
-        content.write(srcLen);     // 源地址长度
-        content.write(srcAddr);    // 源地址
-        content.write(destLen);    // 目的地址长度
-        content.write(destAddr);   // 目的地址
-        content.write(timestamp);  // 当前时间戳
+        content.write(0X80);  // 包序号(1字节)
+        content.write(0x34);       // 帧类型0x34(1字节
+        content.write(destLenFlag);// 目的地址标识(1字节)
+        content.write(destAddr);   // 目的地址(m字节)
+        content.write(srcLenFlag); // 源地址标识(1字节)srcLenFlag
+        content.write(srcAddr);    // 源地址(n字节)
+        content.write(timestamp);  // 时间戳(4字节)
         byte[] contentBytes = content.toByteArray();
 
-        // 6 计算帧长度(从包序号到时间戳的长度)
-        int len = contentBytes.length;
-        byte lenHigh = (byte) ((len >> 8) & 0xFF);
-        byte lenLow = (byte) (len & 0xFF);
-
-        // 7 拼完整帧(不含校验)
+        // 步骤6:拼帧(系统识别码 + 长度字段 + 内容区)
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        bos.write(sysId);      // 系统识别码
-        bos.write(lenHigh);    // 长度高
-        bos.write(lenLow);     // 长度低
+        bos.write(sysId);      // 系统识别码(3字节)
+        bos.write(lenHigh);    // 长度高字节(包含自身2字节的总长度)
+        bos.write(lenLow);     // 长度低字节
         bos.write(contentBytes);
 
-        // 8 计算异或校验
+        // 步骤7:计算并添加异或校验(仅计算不含校验位的帧体)
         byte[] noCheck = bos.toByteArray();
-        byte xor = 0x00;
-        for (byte b : noCheck) xor ^= b;
-        bos.write(xor); // 校验位
+        byte xor = calculateXorCheck(noCheck);
+        bos.write(xor);
 
         byte[] result = bos.toByteArray();
-
         System.out.println("结束帧应答: " + printHex(result));
         return result;
     }
@@ -302,4 +290,72 @@ public class ProtocolUtils {
         for (byte b : bytes) sb.append(String.format("%02X ", b));
         return sb.toString().trim();
     }
+    /**
+     * 核心工具方法:根据地址标识计算地址字节长度
+     * 规则:标识值转10进制 → 奇数+1 → 除以2
+     */
+    private static int calculateAddrByteLength(int lenFlag) {
+        int temp = lenFlag;
+        // 奇数则+1
+        if (temp % 2 != 0) {
+            temp += 1;
+        }
+        // 除以2得到字节长度
+        return temp / 2;
+    }
+    /**
+     * 核心工具:正向解析源/目的地址信息(贴合实际报文结构)
+     * 逻辑:
+     * 1. 正向定位源地址标识(索引7)、源地址、目的地址标识、目的地址
+     * 2. 找 0x01 2C 验证地址段结束位置,确保解析合法
+     * @return 数组格式:[srcLenFlag, srcAddrByteLen, srcAddrStart, srcAddrEnd, destLenFlag, destAddrByteLen, destAddrStart, destAddrEnd]
+     */
+    private static int[] extractAddrInfo(byte[] request) {
+        if (request == null || request.length < 25) { // 适配实际报文最小长度(82字节)
+            throw new IllegalArgumentException("请求帧长度不足,无法解析地址信息,长度=" + (request == null ? 0 : request.length));
+        }
+
+        // ========== 步骤1:正向定位核心索引(贴合协议固定结构) ==========
+        int srcLenIndex = 7; // 源地址标识固定在索引7
+        int srcLenFlag = request[srcLenIndex] & 0xFF;
+        int srcAddrByteLen = calculateAddrByteLength(srcLenFlag);
+        int srcAddrStart = srcLenIndex + 1;
+        int srcAddrEnd = srcAddrStart + srcAddrByteLen;
+
+        // 目的地址标识 = 源地址结束索引
+        int destLenIndex = srcAddrEnd;
+        int destLenFlag = request[destLenIndex] & 0xFF;
+        int destAddrByteLen = calculateAddrByteLength(destLenFlag);
+        int destAddrStart = destLenIndex + 1;
+        int destAddrEnd = destAddrStart + destAddrByteLen;
+
+        // ========== 步骤2:验证地址段结束位置(找 0x01 2C) ==========
+        int deviceCodeIndex = destAddrEnd;
+        // 校验设备编码+功能码是否为 0x01 2C(地址段后必须紧跟)
+        if (deviceCodeIndex + 1 >= request.length
+                || request[deviceCodeIndex] != 0x01
+                || request[deviceCodeIndex + 1] != 0x2C) {
+            throw new IllegalArgumentException("地址段结束后未找到 0x01 2C 序列,地址解析异常:设备编码索引=" + deviceCodeIndex);
+        }
+
+        // ========== 步骤3:范围最终校验 ==========
+        // 源地址范围校验
+        if (srcAddrStart < 8 || srcAddrEnd > destLenIndex) {
+            throw new IllegalArgumentException(
+                    String.format("源地址范围异常:标识索引=%d 标识值=%d 计算长度=%d 起始=%d 结束=%d",
+                            srcLenIndex, srcLenFlag, srcAddrByteLen, srcAddrStart, srcAddrEnd));
+        }
+        // 目的地址范围校验
+        if (destAddrStart < (destLenIndex + 1) || destAddrEnd > deviceCodeIndex) {
+            throw new IllegalArgumentException(
+                    String.format("目的地址范围异常:标识索引=%d 标识值=%d 计算长度=%d 起始=%d 结束=%d",
+                            destLenIndex, destLenFlag, destAddrByteLen, destAddrStart, destAddrEnd));
+        }
+
+        return new int[]{
+                srcLenFlag, srcAddrByteLen, srcAddrStart, srcAddrEnd,
+                destLenFlag, destAddrByteLen, destAddrStart, destAddrEnd
+        };
+    }
+
 }