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→非法)。

2. 报错调用链分析

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(文本),却被当作十六进制字符串解码,需确认业务逻辑:

  • 若业务需要解码十六进制:让字符串来源(前端 / 设备)改为传递标准十六进制字符串(比如 646576696365313233 是 device123 的十六进制编码);
  • 若业务不需要解码:直接使用原字符串,删除 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 不改变数据语义(比如 12 和 012 解码后字节数组长度不同,但 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. 用安全解码方法封装,避免重复报错。

 

评论 11
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值