You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ScalaPB toProtoString生成QR码无法解码问题咨询

问题背景

我定义了一个名为RegistrationInfo的Protobuf对象:

message RegistrationInfo {
  string serverID = 1;
  string serverIP = 2;
  string alias = 3;
  string rootCA = 4;
}

在Scala后端中,我使用zxing库的QRCodeGenerator类将该对象生成QR图片:

def init(): Unit = {
  // 创建RegistrationInfo实例
  val regInfo = RegistrationInfo(SERVER_ID, SERVER_IP, s"${OS.getOS.toString} Server", Base64.getEncoder.encodeToString(SSLManager.loadRootCertificate().getEncoded))
  encodedQRCode = generateEncodedQRCode(regInfo.toProtoString)
}

private def generateEncodedQRCode(text: String): String = {
  val qrCodeWriter = new QRCodeWriter()

  // 设置QR码属性
  val hintMap = new util.HashMap[EncodeHintType, Any]()
  hintMap.put(EncodeHintType.CHARACTER_SET, "UTF-8")

  // 生成QR码位矩阵
  val bitMatrix = qrCodeWriter.encode(text, BarcodeFormat.QR_CODE, 150, 150, hintMap)

  // 将位矩阵写入字节数组(PNG格式)
  val outputStream = new java.io.ByteArrayOutputStream()
  MatrixToImageWriter.writeToStream(bitMatrix, "PNG", outputStream)

  // 返回图片的Base64编码字符串
  Base64.getEncoder.encodeToString(outputStream.toByteArray)
}

我的NextJS应用请求该Base64编码字符串并提供下载链接,流程正常,可成功下载QR图片。但后端处理下载后的QR图片时,使用对应解码代码捕获到NotFoundException(zxing无法找到有效QR码),不过将图片上传至zxing官方解码工具可成功解码并查看数据。

排查过程
  • 移除NextJS环节,直接将生成结果传入解码函数,仍抛出相同异常,排除前端问题;
  • 调试确认生成和解码的字节数组、Base64字符串完全一致;
  • 生成"hello world"的QR码可正常解码,排除基础逻辑问题;
  • 切换为JsonFormat.printer.print(regInfo)仍无效,将rootCA替换为普通字符串后解码成功,确认rootCA的Base64编码为问题关键。
咨询问题

如何将rootCA以可读格式存入proto,同时保证QR码可正常解码?


解决方案

1. 调整QR码生成参数,提升版本与容错率

RootCA的Base64编码字符串较长,固定150x150的尺寸可能导致内容超出当前QR码版本的容量,或容错率不足。修改生成逻辑,增加容错配置并放宽尺寸限制:

private def generateEncodedQRCode(text: String): String = {
  val qrCodeWriter = new QRCodeWriter()

  val hintMap = new util.HashMap[EncodeHintType, Any]()
  hintMap.put(EncodeHintType.CHARACTER_SET, "UTF-8")
  // 设置最高容错级别,允许QR码被遮挡或模糊时仍可识别
  hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H)
  // 让zxing自动选择合适的QR码版本,避免内容溢出
  hintMap.put(EncodeHintType.QR_VERSION, 0)

  // 增大QR码尺寸,适配长内容
  val bitMatrix = qrCodeWriter.encode(text, BarcodeFormat.QR_CODE, 300, 300, hintMap)

  val outputStream = new java.io.ByteArrayOutputStream()
  MatrixToImageWriter.writeToStream(bitMatrix, "PNG", outputStream)

  Base64.getEncoder.encodeToString(outputStream.toByteArray)
}

2. 将RootCA改用Protobuf的bytes类型存储

把proto中的rootCA字段从string改为bytes,直接存储证书的原始字节数组,无需Base64编码,减少内容长度:

message RegistrationInfo {
  string serverID = 1;
  string serverIP = 2;
  string alias = 3;
  bytes rootCA = 4; // 替换为bytes类型
}

Scala代码中直接传入证书字节数组,若需要可读格式,使用Protobuf文本格式序列化:

val regInfo = RegistrationInfo(
  SERVER_ID, 
  SERVER_IP, 
  s"${OS.getOS.toString} Server", 
  SSLManager.loadRootCertificate().getEncoded // 无需Base64编码
)
// 转换为可读的Protobuf文本格式
import com.google.protobuf.TextFormat
val text = TextFormat.printer.print(regInfo)
encodedQRCode = generateEncodedQRCode(text)

解码时直接从bytes字段提取证书:

val regInfo = RegistrationInfo.parseFrom(decodedBytes)
val certificate = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(regInfo.getRootCA))

3. 优化解码代码的Hint配置

解码时添加TRY_HARDER参数,让zxing更彻底地扫描图片,提升识别成功率:

def decodeQRCode(imageBytes: Array[Byte]): String = {
  val hintMap = new util.HashMap[DecodeHintType, Any]()
  hintMap.put(DecodeHintType.CHARACTER_SET, "UTF-8")
  // 强制zxing尝试所有可能的解码方式,适合复杂或高密度的QR码
  hintMap.put(DecodeHintType.TRY_HARDER, Boolean.TRUE)
  hintMap.put(DecodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H)

  val image = ImageIO.read(new ByteArrayInputStream(imageBytes))
  val source = new BufferedImageLuminanceSource(image)
  val bitmap = new BinaryBitmap(new HybridBinarizer(source))
  val reader = new MultiFormatReader()
  
  reader.decode(bitmap, hintMap).getText
}

4. 使用Protobuf文本格式替代JSON

如果必须保留可读格式,优先使用Protobuf官方的文本格式(TextFormat)而非JSON,它对bytes类型会自动进行Base64编码,且比JSON更紧凑,更适合QR码存储。


内容的提问来源于stack exchange,提问作者Kris Rice

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 07:17:08