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
相关产品推荐
相关产品推荐

