Browse Source

优化推送能耗网关心跳和能耗设备数据推送能耗平台逻辑,增加校验平台服务器返回报文的逻辑

james 3 ngày trước cách đây
mục cha
commit
642e9221fc

+ 152 - 54
service-ems/service-ems-biz/src/main/java/com/usky/ems/protocol/TcpClient.java

@@ -1,7 +1,11 @@
 package com.usky.ems.protocol;
 
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.usky.ems.util.AesUtil;
 import org.dom4j.Document;
+import org.dom4j.DocumentException;
 import org.dom4j.DocumentHelper;
 import org.dom4j.Element;
 import org.slf4j.Logger;
@@ -11,6 +15,9 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -169,8 +176,22 @@ public class TcpClient {
         try {
             String heartbeatXml = com.usky.ems.util.XmlBuilder.buildHeartbeatRequest(buildingId, gatewayId);
             sendPacket(NetworkPacket.TYPE_HEARTBEAT, heartbeatXml, false);
-            logger.debug("心跳发送成功");
-            return true;
+
+            NetworkPacket response1 = receivePacket();
+            if (response1 != null  && response1.getType() == NetworkPacket.TYPE_HEARTBEAT) {
+                // 直接转换为可读XML(无需解密)
+                String heartbeatResponseXml = new String(response1.getData(), java.nio.charset.StandardCharsets.UTF_8);
+                System.out.println("可读报文:\n" + heartbeatResponseXml);
+                boolean success = this.parseHeartbeatResult(heartbeatResponseXml);
+                if (success) {
+                    logger.info("发送心跳成功");
+                    return true;
+                }else{
+                    logger.info("发送心跳失败");
+                    return false;
+                }
+            }
+            return false;
         } catch (Exception e) {
             logger.error("发送心跳失败", e);
             return false;
@@ -191,69 +212,95 @@ public class TcpClient {
 
         try {
             sendPacket(NetworkPacket.TYPE_ENERGY_DATA, xmlData, true);
-            logger.info("能耗数据发送成功");
 
-            // 接收服务端响应
             NetworkPacket response = receivePacket();
-            if (response != null && response.getType() == NetworkPacket.TYPE_ENERGY_DATA) {
-                // 能耗数据响应需要解密
-                try {
-                    byte[] responseData = response.getData();
-                    String responseXml = null;
-                    Exception lastException = null;
-
-                    // 方法1:首先尝试直接解密字节数组
-                    try {
-                        responseXml = AesUtil.decrypt(authKey,responseData);
-                        logger.debug("直接解密字节数组成功");
-                    } catch (Exception e1) {
-                        lastException = e1;
-                        logger.debug("直接解密字节数组失败: {}", e1.getMessage());
-
-                        // 方法2:如果直接解密失败,尝试作为Base64字符串处理
-                        // 先检查数据是否可能是Base64字符串(只包含Base64字符)
-                        if (responseXml == null) {
-                            try {
-                                String dataString = new String(responseData, java.nio.charset.StandardCharsets.UTF_8);
-                                // 检查是否只包含Base64字符(A-Z, a-z, 0-9, +, /, =)
-                                if (dataString.matches("^[A-Za-z0-9+/=]+$")) {
-                                    // Base64解码后再解密
-                                    byte[] decodedBytes = java.util.Base64.getDecoder().decode(dataString);
-                                    responseXml = AesUtil.decrypt(authKey,decodedBytes);
-                                    logger.debug("Base64解码后解密成功");
-                                } else {
-                                    logger.debug("数据不是Base64格式,跳过Base64解码");
-                                }
-                            } catch (Exception e2) {
-                                lastException = e2;
-                                logger.debug("Base64解码后解密失败: {}", e2.getMessage());
-                            }
-                        }
-                    }
-
-                    if (responseXml != null) {
-                        logger.debug("服务端响应: {}", responseXml);
-                    } else {
-                        throw lastException != null ? lastException : new Exception("所有解密方法都失败");
-                    }
-                } catch (Exception e) {
-                    logger.warn("解密服务端响应失败", e);
-                    logger.debug("响应数据长度: {}, 前100字节: {}",
-                            response.getData() != null ? response.getData().length : 0,
-                            response.getData() != null && response.getData().length > 0
-                                    ? java.util.Arrays.toString(java.util.Arrays.copyOf(response.getData(), Math.min(100, response.getData().length)))
-                                    : "null");
+            if (response != null  && response.getType() == NetworkPacket.TYPE_ENERGY_DATA) {
+                // 直接转换为可读XML(无需解密)
+                String readable = new String(response.getData(), java.nio.charset.StandardCharsets.UTF_8);
+                System.out.println("可读报文:\n" + readable);
+                boolean success = this.parseEnergyDataResult(readable);
+                if (success) {
+                    logger.info("能耗数据发送成功");
+                    return true;
+                }else{
+                    logger.info("能耗数据发送失败");
+                    return false;
                 }
-                return true;
             }
+            return false;
 
-            return true;
         } catch (Exception e) {
             logger.error("发送能耗数据失败", e);
             return false;
         }
     }
 
+    /**
+     * 将TCP报文字节数组直接转换为可读格式(无需解密)
+     * @param dataBytes 原始报文字节数组
+     * @param charset 报文字符编码(默认UTF-8)
+     * @param format 报文格式(xml/json/plain,plain为普通文本)
+     * @return 可读的报文字符串
+     */
+    public String convertToReadablePacket(byte[] dataBytes, String charset, String format) {
+        if (dataBytes == null || dataBytes.length == 0) {
+            return "空报文";
+        }
+
+        // 兜底处理字符编码,默认用UTF-8
+        String actualCharset = (charset == null || charset.isEmpty()) ? "UTF-8" : charset;
+
+        try {
+            // 第一步:直接将字节数组转字符串(核心,无需解密)
+            String rawText = new String(dataBytes, actualCharset);
+            logger.debug("原始文本报文:{}", rawText);
+
+            // 第二步:根据格式美化
+            switch (format.toLowerCase()) {
+                case "xml":
+                    return formatXml(rawText); // XML格式化(带缩进)
+                case "json":
+                    return formatJson(rawText); // JSON格式化(带缩进)
+                case "plain":
+                default:
+                    return rawText; // 普通文本直接返回
+            }
+        } catch (Exception e) {
+            logger.warn("转换为文本格式失败,返回十六进制格式:{}", e.getMessage());
+            // 转换失败时,返回十六进制(所有二进制数据都能读)
+            return bytesToHex(dataBytes);
+        }
+    }
+
+    /**
+     * XML格式化(美化缩进)
+     */
+    private String formatXml(String xmlText) throws DocumentException {
+        Document document = DocumentHelper.parseText(xmlText);
+        return document.asXML();
+    }
+
+    /**
+     * JSON格式化(美化缩进)
+     */
+    private String formatJson(String jsonText) throws Exception {
+        ObjectMapper mapper = new ObjectMapper();
+        Object jsonObj = mapper.readValue(jsonText, Object.class);
+        return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObj);
+    }
+
+    /**
+     * 字节数组转十六进制字符串(兜底方案)
+     * 格式:00 1A 2B ... (便于排查二进制数据问题)
+     */
+    private String bytesToHex(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%02X ", b));
+        }
+        return "十六进制格式:" + sb.toString().trim();
+    }
+
     /**
      * 发送数据包
      *
@@ -326,6 +373,7 @@ public class TcpClient {
         return NetworkPacket.decode(packetBytes);
     }
 
+
     /**
      * 从XML中解析随机序列
      */
@@ -366,6 +414,56 @@ public class TcpClient {
         return false;
     }
 
+    /**
+     * 从XML中解析推送能耗数据响应结果
+     */
+    private boolean parseEnergyDataResult(String xml) {
+        try {
+            Document document = DocumentHelper.parseText(xml);
+            Element root = document.getRootElement();
+            Element dataElement = root.element("data");
+            if (dataElement == null) {
+                logger.warn("XML中未找到<data>节点");
+            }
+
+            // 4. 获取<ack>子节点的文本值
+            Element ackElement = dataElement.element("ack");
+            if (ackElement == null) {
+                logger.warn("XML中未找到<ack>节点");
+            }
+
+            return "OK".equals(ackElement.getTextTrim());
+        } catch (Exception e) {
+            logger.error("解析认证结果失败", e);
+        }
+        return false;
+    }
+
+    /**
+     * 从XML中解析推送能耗数据响应结果
+     */
+    private boolean parseHeartbeatResult(String xml) {
+        try {
+            Document document = DocumentHelper.parseText(xml);
+            Element root = document.getRootElement();
+            Element dataElement = root.element("heart_beat");
+            if (dataElement == null) {
+                logger.warn("XML中未找到<heart_beat>节点");
+            }
+
+            // 4. 获取<ack>子节点的文本值
+            Element ackElement = dataElement.element("time");
+            if (ackElement == null) {
+                logger.warn("XML中未找到<time>节点");
+            }
+
+            return StringUtils.isNotBlank(ackElement.getTextTrim());
+        } catch (Exception e) {
+            logger.error("解析认证结果失败", e);
+        }
+        return false;
+    }
+
     public boolean isConnected() {
         return connected.get() && socket != null && !socket.isClosed();
     }

+ 18 - 24
service-ems/service-ems-biz/src/main/java/com/usky/ems/util/AesUtil.java

@@ -20,14 +20,16 @@ import cn.hutool.crypto.Padding;
  */
 public class AesUtil {
 
-    /**
-     * 加密能耗XML数据
-     *
-     * @param xml XML字符串
-     * @param key 密钥
-     * @return 加密后的字节数组
-     * @throws Exception 加密异常
-     */
+    public static String decrypt(String key, byte[] cipherText) {
+        try {
+            AES aes = new AES(Mode.CBC, Padding.PKCS5Padding,
+                    key.getBytes(), key.getBytes());
+            return aes.decryptStr(cipherText);
+        } catch (Exception e) {
+            throw new RuntimeException("密钥错误!");
+        }
+    }
+
     public static byte[] encryptEnergyXml(String xml, String key) throws Exception {
         Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
         byte[] uploadKeyBytes = key.getBytes();
@@ -37,21 +39,13 @@ public class AesUtil {
         return cipher.doFinal(xml.getBytes(StandardCharsets.UTF_8));
     }
 
-    /**
-     * 解密能耗XML数据
-     *
-     * @param encryptedData 加密后的字节数组
-     * @param key           密钥
-     * @return 解密后的XML字符串
-     * @throws Exception 解密异常
-     */
-    public static  String decrypt(String key, byte[] cipherText) {
-        try {
-            AES aes = new AES(Mode.CBC, Padding.PKCS5Padding,
-                    key.getBytes(), key.getBytes());
-            return aes.decryptStr(cipherText);
-        } catch (Exception e) {
-            throw new RuntimeException("密钥错误!");
-        }
+    public static String decryptEnergyXml(byte[] encryptedXml, String key) throws Exception {
+        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+        byte[] uploadKeyBytes = key.getBytes(StandardCharsets.UTF_8);
+        SecretKeySpec secretKeySpec = new SecretKeySpec(uploadKeyBytes, "AES");
+        IvParameterSpec ivParameterSpec = new IvParameterSpec(uploadKeyBytes);
+        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
+        byte[] decryptedBytes = cipher.doFinal(encryptedXml);
+        return new String(decryptedBytes, StandardCharsets.UTF_8);
     }
 }

+ 1 - 1
service-ems/service-ems-biz/src/main/resources/bootstrap.yml

@@ -1,6 +1,6 @@
 # Tomcat
 server:
-  port: 9888
+  port: 9903
 
 # Spring
 spring: