MQTT报错:Exception in thread main java.lang.at io.github.pnoker.common.sdk.utils.ParseUtils.decodeHex

运行报错:Exception in thread "main" java.lang.at io.github.pnoker.common.sdk.utils.ParseUtils.decodeHex(ParseUtils.java:98)
Odd number of characters.
at io.github.pnoker.driver.service.impl.DriverCustomsServiceImpl.write(DriverCustomsServiceImpl.java:174)at io.github.pnoker.driver.service.impl.Tess.main(Tess.java:29)Disconnected from the target VM, address: '127.0.0.1:53527', transport: 'socket'

运行界面:

这个报错的核心是 十六进制字符串解码时长度为奇数

触发点在 ParseUtils.decodeHex 方法 ------ 十六进制字符串(Hex String)的长度必须是 偶数 (1 个字节 = 2 个十六进制字符,如 1A 2B),若传入长度为奇数(如 1 1A3),解码时会直接抛出异常。

一、报错根源拆解

  1. 十六进制解码的规则

我查资料ParseUtils.decodeHex 是解析十六进制字符串的工具方法(类似 Apache Commons Codec 的 Hex.decodeHex),要求:

  • 输入字符串只能包含 0-9、A-F、a-f 字符(十六进制合法字符);
  • 字符串长度必须是 偶数 (比如 2A0611A49323 长度 12→合法,2A0611A4932 长度 11→非法)。
  1. 报错调用链分析

    Tess.main(29行) → DriverCustomsServiceImpl.write(174行) → ParseUtils.decodeHex(98行)

→ 问题出在:DriverCustomsServiceImpl.write 第 174 行,传入 decodeHex 的字符串是「长度为奇数」或「包含非法字符」的非标准十六进制字符串。

二、分步解决方案(按优先级排序)

步骤 1:定位传入 decodeHex 的异常字符串(关键)

首先要找到「到底是什么字符串」触发了报错,在 DriverCustomsServiceImpl.write 第 174 行添加日志,打印传入的字符串:

复制代码
// 找到 DriverCustomsServiceImpl.java 第174行,修改前先打印参数
String hexStr = "你当前传入的字符串"; // 替换为实际变量名(比如 param、data 等)
System.out.println("传入 decodeHex 的字符串:" + hexStr);
System.out.println("字符串长度:" + hexStr.length());
System.out.println("字符串是否仅含十六进制字符:" + hexStr.matches("[0-9A-Fa-f]+"));

// 原调用代码(第174行)
byte[] result = ParseUtils.decodeHex(hexStr);

重启应用后,查看控制台输出,重点关注 3 点:

  • 字符串长度是否为奇数(比如 1、3、5 等);
  • 字符串是否包含非十六进制字符(比如字母 G、中文、空格等);
  • 字符串来源(是前端传入、设备推送、配置文件读取?)。

步骤 2:修复异常字符串(按场景处理)

根据步骤 1 打印的结果,分 3 种常见场景修复:

场景 1:字符串长度为奇数(最常见)

比如传入 2A0611A4932(长度 11)、1(长度 1),需补充 1 个字符(默认补 0,或检查来源是否漏传)。

  • 修复方案:在调用 decodeHex 前,判断长度是否为奇数,若为奇数则在开头 / 结尾补 0(根据业务场景选择,通常补 0):

    复制代码
    String hexStr = "你传入的字符串";
    // 长度为奇数时,开头补0(推荐,符合十六进制编码习惯)
    if (hexStr.length() % 2 != 0) {
        hexStr = "0" + hexStr; 
        System.out.println("补0后的字符串:" + hexStr); // 比如 "2A0611A4932" → "02A0611A4932"
    }
    // 再解码
    byte[] result = ParseUtils.decodeHex(hexStr);
  • 注意:若业务不允许补 0(比如字符串是设备唯一标识),需检查字符串来源(如前端 / 设备),确认是否漏传了 1 个字符(比如设备本应发送 2A0611A49323,却少发了最后 1 个字符)。

场景 2:字符串包含非十六进制字符

比如传入 2A06G1A49323(含字母 G)、2A 06 11(含空格)、中文123,需过滤非法字符。

  • 修复方案:用正则表达式保留仅十六进制字符(0-9、A-F、a-f):

    复制代码
    String hexStr = "你传入的字符串";
    // 过滤非十六进制字符(只保留 0-9、A-F、a-f)
    hexStr = hexStr.replaceAll("[^0-9A-Fa-f]", "");
    System.out.println("过滤后的字符串:" + hexStr);
    // 再判断长度是否为奇数(过滤后可能长度变化)
    if (hexStr.length() % 2 != 0) {
        hexStr = "0" + hexStr;
    }
    // 解码
    byte[] result = ParseUtils.decodeHex(hexStr);

场景 3:字符串是 "普通文本" 而非十六进制(误传)

比如传入 device123(普通设备编号)、hello(文本),却被当作十六进制字符串解码,需确认业务逻辑:

  • 若业务需要解码十六进制:让字符串来源(前端 / 设备)改为传递标准十六进制字符串(比如 646576696365313233device123 的十六进制编码);
  • 若业务不需要解码:直接使用原字符串,删除 decodeHex 调用(比如只是保存设备编号,无需转字节数组)。

步骤 3:优化代码健壮性(避免后续报错)

ParseUtils.decodeHex 调用前添加完整校验,防止非法字符串传入:

复制代码
/**
 * 安全解码十六进制字符串(含校验和容错)
 * @param hexStr 待解码字符串
 * @return 解码后的字节数组
 */
private byte[] safeDecodeHex(String hexStr) {
    if (hexStr == null || hexStr.isEmpty()) {
        throw new IllegalArgumentException("十六进制字符串不能为空");
    }
    // 1. 过滤非十六进制字符
    String cleanHex = hexStr.replaceAll("[^0-9A-Fa-f]", "");
    // 2. 处理奇数长度(开头补0)
    if (cleanHex.length() % 2 != 0) {
        cleanHex = "0" + cleanHex;
        System.out.println("警告:十六进制字符串长度为奇数,已补0 → " + cleanHex);
    }
    // 3. 解码(调用原工具方法)
    return ParseUtils.decodeHex(cleanHex);
}

// 第174行调用时,改用安全解码方法
byte[] result = safeDecodeHex(hexStr);

三、验证修复效果

  1. 重启应用后,查看控制台日志:

    • 若打印 "补 0 后的字符串""过滤后的字符串",说明校验生效;
    • 若不再抛出 Odd number of characters 异常,且解码后的字节数组符合预期(比如设备编号、数据内容正确),则修复成功。
  2. 若仍报错:

    • 检查 ParseUtils.decodeHex 源码,确认是否有其他校验(比如对空字符串、长度为 0 的判断);
    • 若字符串来源是设备推送,用抓包工具(如 Wireshark、tcpdump)捕获原始数据,确认设备发送的字符串是否符合标准。

四、常见问题补充

  1. 为什么补 0 而不是删最后 1 个字符?

    • 十六进制编码中,高位补 0 不改变数据语义(比如 12012 解码后字节数组长度不同,但 012 是合法的 3 字节数据),而删字符可能导致数据丢失(比如 123 删为 12,可能丢失原数据的最后 4 位)。
  2. 如何确认字符串是否为标准十六进制?

    • 用在线工具验证:将字符串粘贴到 十六进制解码工具,若能正常解码为预期内容(如设备编号、数据),则为标准十六进制。
  3. ParseUtils.decodeHex 本身有 bug?

    • 替换为成熟工具类(避免自定义工具类的潜在问题),比如 Apache Commons Codec:

      复制代码
      <!-- pom.xml 引入依赖(Maven) -->
      <dependency>
          <groupId>commons-codec</groupId>
          <artifactId>commons-codec</artifactId>
          <version>1.15</version>
      </dependency>
      
      // 用 Commons Codec 解码(自动处理部分异常,更稳定)
      import org.apache.commons.codec.binary.Hex;
      byte[] result = Hex.decodeHex(cleanHex.toCharArray());

总结

这个报错的核心是「十六进制字符串格式非法」,修复流程:

  1. 打印传入 decodeHex 的字符串,确认长度和内容;
  2. 过滤非法字符 + 处理奇数长度(补 0);
  3. 用安全解码方法封装,避免重复报错。
相关推荐
小江的记录本2 分钟前
【Spring AI】Spring AI中RAG误触发与系统提示词泄露问题解决方案(完整版+代码方案)
java·人工智能·spring boot·后端·python·spring·面试
YY&DS8 分钟前
Qt Designer 自定义控件已提升后,如何修改提升类
开发语言·qt
勇往直前plus10 分钟前
Python 属性访问与操作全解析:内置函数、魔法方法与描述符深度指南
java·网络·python
Arenaschi16 分钟前
关于GPT的版特点
java·网络·人工智能·windows·python·gpt
人道领域16 分钟前
【LeetCode刷题日记】108.将有序数组转换为二叉搜索树
java·算法·leetcode
右耳朵猫AI18 分钟前
Rust技术周刊 2026年第19周
开发语言·后端·rust
橙淮23 分钟前
并发编程(五)
java
Leweslyh28 分钟前
基于 Confucius 架构的无人集群网络控制原语解析
开发语言·网络·php
过期动态30 分钟前
【LeetCode 热题 100】无重复字符的最长子串
java·数据结构·spring boot·算法·leetcode·职场和发展
Yeats_Liao32 分钟前
好复杂的 IoT 世界:工业数据采集技术栈全景解析
java·物联网·struts