Java读取及生成pb文件并转换jsonString,pb与jsonStr,pb与byte[]互转

  1. 读取pb及生成pb文件
  2. pb文件转换jsonString
  3. 二进制pb转换jsonString
  4. 赋值(有空或者类型不对应会无法赋值及报错)

1. 效果图

在这里插入图片描述

2. 原理

2.1 Protocol Buffers是什么

协议缓冲区是用于序列化结构化数据的与语言无关、与平台无关的可扩展机制。

协议缓冲区(Protocol Buffers又名protobuf)是 Google 的语言中立、平台中立、可扩展的 序列化结构化数据的机制 – 想想 XML,但更小、更快、 简单。只需定义一次数据的结构,然后就可以 使用特殊生成的源代码轻松编写和读取结构化数据,往返各种数据流并使用多种语言。

2.2 支持的语言

协议缓冲区目前支持Java,Python,Objective-C,中生成的代码 和C++。使用新的 proto3 语言版本,还可以使用 Kotlin, Dart,Go,Ruby,PHP和C#,还有更多的语言即将推出。

2.3 根据.proto生成.java

protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto

2.4 初始化及构建pb,读取,转jsonString

标准消息方法:

  • isInitialized():检查是否已设置所有必填字段。
  • toString():返回消息的人类可读表示形式, 对于调试特别有用。
  • mergeFrom(Message other):(仅限构建器)合并 的内容 other 到此消息中,覆盖奇异标量字段,合并复合 字段,并连接重复字段。
  • clear():(仅限构建器)将所有字段清除回空状态。

解析和序列化:

  • byte[] toByteArray();:序列化消息并返回一个字节数组 包含其原始字节。
  • static Person parseFrom(byte[] data);:解析来自给定的消息 字节数组。
  • void writeTo(OutputStream output);:序列化消息并写入它 到一个 OutputStream.
  • static Person parseFrom(InputStream input);:读取和分析消息 从 InputStream.

2.5 pb对象通过JsonFormat.merge赋值时当属性字段不一致时,无法赋值或者会报错

报错:long转string,无法赋值不会显式报错
boolean转 int ,无法赋值直接报错

SimplePropertyPreFilter filter = new SimplePropertyPreFilter();
filter.getIncludes().add("bcFlag");
System.out.println(JSONObject.toJSONString(obj, filter));

Person.Builder personBuilder= Person.newBuilder();
JsonFormat.merge(JSON.toJSONString(obj,filter)), personBuilder);
if(obj.getBcFlag() != null){
   personBuilder.setBcFlag(obj.getBcFlag() ? 1 : 0);
}
if(obj.getGeometry() != null){
  personBuilder.setGeometry(geoBytes2Geom(obj.getGeometry()));
}

解决办法:赋值前需要filter过滤
手动判断有值并赋值;

3. 源码

3.1 address.proto

syntax = "proto3";

package tutorial;

option java_multiple_files = true;
option java_package = "com.example.tutorial.protos";
option java_outer_classname = "AddressBookProtos";

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
  Geometry geometry=4;
  repeated PhoneNumber phones = 5;

  enum PhoneType {
    PHONE_TYPE_UNSPECIFIED = 0;
    PHONE_TYPE_MOBILE = 1;
    PHONE_TYPE_HOME = 2;
    PHONE_TYPE_WORK = 3;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  message Geometry {
    repeated Point point=1;
  }

  message Point{
    double longitude = 1;
    double latitude = 2;
    double altitude = 3;
  }

}

message AddressBook {
  repeated Person people = 1;
}

3.2 PbParseUtil.java

package com.test.utils;

import com.example.tutorial.protos.AddressBook;
import com.example.tutorial.protos.Person;
import com.googlecode.protobuf.format.JsonFormat;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/*************************************
 *Class Name: PbParseUtil
 *Description: <pb读取转换工具类>
 *@author: Seminar
 *@create: 2023/7/31
 *@since 1.0.0
 *************************************/
public class PbParseUtil {

    // 打印pb的所有字段
    static void Print(AddressBook addressBook) {
        for (Person person : addressBook.getPeopleList()) {
            System.out.println("Person ID: " + person.getId());
            System.out.println("  Name: " + person.getName());
            if (StringUtils.isNotEmpty(person.getEmail())) {
                System.out.println("  E-mail address: " + person.getEmail());
            }

            for (Person.PhoneNumber phoneNumber : person.getPhonesList()) {
                switch (phoneNumber.getType()) {
                    case PHONE_TYPE_MOBILE:
                        System.out.print("  Mobile phone #: ");
                        break;
                    case PHONE_TYPE_HOME:
                        System.out.print("  Home phone #: ");
                        break;
                    case PHONE_TYPE_WORK:
                        System.out.print("  Work phone #: ");
                        break;
                }
                System.out.println(phoneNumber.getNumber());
            }

            if (person.getGeometry() != null) {
                for (Person.Point point : person.getGeometry().getPointList()) {
                    System.out.println("lon: " + point.getLongitude() + ",lat: " + point.getLatitude() + ",alt: " + point.getAltitude());
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {

        // 协议缓冲区编译器生成的消息类都是 不可变 。消息对象一旦构造完成,就无法修改,
        // 要构造消息,必须首先构造一个 生成器,将要设置的任何字段设置为所选值,然后调用build() 方法。

        // 初始化方法1
        Person john = Person.newBuilder()
                .setId(1234)
                .setName("John Doe")
                .setEmail("jdoe@example.com")
                .addPhones(
                        Person.PhoneNumber.newBuilder()
                                .setNumber("555-4321")
                                .setType(Person.PhoneType.PHONE_TYPE_HOME))
                .build();

        // 初始化方法2
        Person.Builder person = Person.newBuilder();
        person.setEmail("1222@qq.com");
        person.setName("Lucy");
        person.setId(1).addPhones(Person.PhoneNumber.newBuilder()
                .setNumber("12634524230")
                .setType(Person.PhoneType.PHONE_TYPE_MOBILE));


        // java pb转二进制
        byte[] personPb = john.toByteArray();

        // java pb转pb文件
        byte[] personPb2 = person.build().toByteArray();
        String pbFilePath = System.getProperty("user.dir") + File.separator + "person.pb";
        try (FileOutputStream fileWriter = new FileOutputStream(pbFilePath)) {
            fileWriter.write(personPb2);
        }

        // pb二进制转Java对象
        // pb文件转Java对象
        Person person1 = Person.parseFrom(personPb);
        Person person2 = Person.parseFrom(new FileInputStream(pbFilePath));

        // pb文件转Java对象
        Person.Builder person3 = Person.newBuilder();
        person3.mergeFrom(new FileInputStream(pbFilePath));
        // 某些字段没有的,需要单独设置
        Person.Geometry.Builder geometryBuilder = Person.Geometry.newBuilder();
        for (int i = 0; i < 3; i++) {
            geometryBuilder.addPoint(Person.Point.newBuilder().setLongitude(113.222222 + i * 1.5).setLatitude(40.1 + i * 0.89).setAltitude(40 + i * 0.45).build());
        }
        person3.setGeometry(geometryBuilder.build());

        System.out.println("person1 name: " + person1.getName());
        System.out.println("person2 name: " + person2.getName());
        System.out.println("person3 name: " + person2.getName());

        AddressBook address = AddressBook.newBuilder()
                .addPeople(person1).addPeople(person3).build();
        Print(address);

        // pb转jsonString
        String str = JsonFormat.printToString(address);
        System.out.println("jsonStr: " + str);
    }
}

参考

package com.shop.jieyou.controller; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.shop.jieyou.common.Result; import com.shop.jieyou.entity.UserItem; import com.shop.jieyou.service.ItemService; import com.shop.jieyou.service.PythonService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.net.URL; import java.net.URLConnection; import java.nio.file.Files; import java.util.*; import java.util.concurrent.TimeUnit; /** * 花卉相关接口控制器 * 提供三大功能: * 1. 获取中国十大名花数据(来自爬虫或缓存) * 2. 手动刷新花卉数据(强制重新爬取) * 3. 基于用户行为的花卉推荐(调用Python协同过滤脚本) */ @RestController @CrossOrigin(origins = "*") // 允许所有域访问,用于前端开发调试(生产环境建议限制域名) @RequestMapping("/api") public class FlowerController { @Autowired private PythonService pythonService; // 注入业务服务层,处理数据获取推荐逻辑 @Autowired private ItemService itemService; /** * 接口:GET /api/flowers * 功能:获取“中国十大名花”数据列表 * 数据来源:可能来自数据库、Redis 缓存 或 调用 Python 爬虫脚本 * * @return Result<List<Map<String, Object>>> 返回包含花卉信息的成功响应 */ @GetMapping("/flowers") public Result<List<Map<String, Object>>> getTopTenFlowers() { try { // 调用服务层获取花卉数据(内部可能带缓存机制) List<Map<String, Object>> flowers = pythonService.getFlowers(); return Result.success(flowers); // 成功返回数据 } catch (Exception e) { // 捕获异常统一返回错误码和消息,避免暴露堆栈给前端 return Result.error("500", "获取花卉数据失败:" + e.getMessage()); } } /** * 接口:POST /api/flowers/refresh * 功能:强制刷新花卉数据缓存,触发重新爬取 * 使用场景:管理员手动更新数据时调用 * * @return Result<Map<String, Object>> 返回刷新结果信息 */ @PostMapping("/flowers/refresh") public Result<Map<String, Object>> refreshData() { try { // TODO: 如果实现了 clearCache 方法,请取消注释调用 // pythonService.clearCache(); // 清除旧缓存,下次 getFlowers 将重新爬取 // 重新获取最新数据(假设此时会触发爬虫) List<Map<String, Object>> flowers = pythonService.getFlowers(); // 构造返回信息 Map<String, Object> data = new HashMap<>(); data.put("message", "数据已刷新"); data.put("count", flowers.size()); return Result.success(data); } catch (Exception e) { return Result.error("500", "刷新失败:" + e.getMessage()); } } // ========== 推荐系统相关常量定义 ========== /** * 输入文件路径:Java 将用户-商品行为数据写入此 JSON 文件供 Python 脚本读取 * 注意:src/main/resources 是编译后打包进 jar 的资源目录,不适合运行时写入! * 建议改为外部路径如 "./data/input.json" */ private static final String INPUT_PATH = "src/main/resources/scripts/input.json"; /** * 输出文件路径:Python 脚本将推荐结果写入此文件Java读取返回给前端 */ private static final String OUTPUT_PATH = "src/main/resources/scripts/output.json"; /** * Python 协同过滤脚本路径 * 注意:resources 目录下的 .py 文件在打包后无法直接作为可执行脚本运行 * 更佳做法是将脚本放在项目外部或使用 ProcessBuilder 启动独立服务 */ private static final String PYTHON_SCRIPT = "src/main/resources/scripts/collaborative.py"; /** * 接口:GET /api/recommend?userId=123 * 功能:为指定用户生成个性化花卉推荐列表 * 实现方式:Java 查询数据库 → 写入 JSON 文件 → 调用 Python 脚本计算 → 读取结果返回 * * @param userId 用户ID,必填参数 * @return Result<JsonNode> 推荐的商品ID数组(如 [101, 105, 108]) */ @GetMapping("/recommend") public Result recommendFlowers(@RequestParam("userId") Long userId) { try { // 1. 获取用户行为数据 List<UserItem> matrix = pythonService.getUserItemMatrix(); // 2. 调用 Python 脚本(通过 stdin/stdout 通信) ProcessBuilder pb = new ProcessBuilder("python", PYTHON_SCRIPT, String.valueOf(userId)); pb.redirectErrorStream(true); // 合错误流 Process process = pb.start(); // 3. 将数据写入脚本的标准输入 ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(process.getOutputStream(), matrix); process.getOutputStream().close(); // 关闭输入,通知Python结束读取 // 4. 读取Python脚本的输出(推荐结果) JsonNode result = mapper.readTree(process.getInputStream()); // 5. 等待脚本执行完毕 int exitCode = process.waitFor(); if (exitCode != 0) { return Result.error("500", "Python script failed with exit code: " + exitCode); } System.out.println(result); return Result.success(result); } catch (Exception e) { e.printStackTrace(); return Result.error("500", "推荐生成失败:" + e.getMessage()); } } @PostMapping("/predict") public ResponseEntity<String> predict(@RequestParam("file") MultipartFile file) { try { // 保存上传的文件到临时路径 String tempDir = System.getProperty("java.io.tmpdir"); File tempFile = new File(tempDir, file.getOriginalFilename()); file.transferTo(tempFile); // 调用 Python 脚本执行预测 ProcessBuilder pb = new ProcessBuilder( "D:\\Python\\python.exe", "D:/DevCode/商城/Shop-master/shop-springboot/src/main/resources/scripts/image_classifier.py", "predict", tempFile.getAbsolutePath() ); pb.redirectErrorStream(true); // 合 stdout 和 stderr Process process = pb.start(); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); StringBuilder output = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { output.append(line); } int exitCode = process.waitFor(); if (exitCode == 0) { return ResponseEntity.ok(output.toString().trim()); } else { return ResponseEntity.status(500).body("{\"error\": \"Prediction failed\"}"); } } catch (Exception e) { return ResponseEntity.status(500).body("{\"error\": \"" + e.getMessage() + "\"}"); } } private static final String PYTHON_EXECUTABLE = "D:\\Python\\python.exe"; // 或 "python3" private static final String INFER_SCRIPT_PATH = "D:/DevCode/商城/Shop-master/shop-springboot/src/main/resources/scripts/python-model/infer.py"; private final ObjectMapper objectMapper = new ObjectMapper(); /** * 对接前端 /api/uploadPython?file=imageUrl */ @PostMapping("/uploadPython") public ResponseEntity<?> classifyImage(@RequestParam String file) { if (file == null || file.trim().isEmpty()) { return ResponseEntity.badRequest().body(error("400", "缺少图片URL")); } File tempImageFile = null; Process process = null; try { // 1. 从 URL 下载图片到本地临时文件 tempImageFile = downloadImageToFile(file); // 2. 调用 Python 脚本处理该临时文件 ProcessBuilder pb = new ProcessBuilder( PYTHON_EXECUTABLE, INFER_SCRIPT_PATH, tempImageFile.getAbsolutePath() // 传入真实文件路径 ); pb.redirectErrorStream(true); // 合 stdout 和 stderr process = pb.start(); // 3. 读取 Python 输出 StringBuilder output = new StringBuilder(); try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { output.append(line).append("\n"); } } int exitCode = process.waitFor(); if (exitCode != 0) { return ResponseEntity.status(500).body(error("500", "Python执行失败")); } // 4. 提取有效 JSON 结果(跳过 TensorFlow 日志) JsonNode resultNode = extractValidJson(output.toString()); if (resultNode == null) { return ResponseEntity.status(500).body(error("500", "无法解析识别结果")); } if (resultNode.has("error")) { return ResponseEntity.status(500).body(error("500", "识别错误:" + resultNode.get("error").asText())); } // 5. 构造成功响应 System.out.println(resultNode); Iterator<Map.Entry<String, JsonNode>> fields = resultNode.fields(); String predictedClass = null; double confidence = 0.0; while (fields.hasNext()) { Map.Entry<String, JsonNode> entry = fields.next(); double prob = entry.getValue().asDouble(); if (prob > confidence) { confidence = prob; predictedClass = entry.getKey(); } } if (predictedClass == null) { return ResponseEntity.status(500).body(error("500", "无法识别任何类别")); } Map<String, Object> result = new HashMap<>(); result.put("predicted_class", predictedClass); result.put("confidence", confidence); return ResponseEntity.ok(success(result)); } catch (Exception e) { e.printStackTrace(); return ResponseEntity.status(500).body(error("500", "服务异常:" + e.getMessage())); } finally { // 6. 确保临时文件被删除 if (tempImageFile != null && tempImageFile.exists()) { boolean deleted = tempImageFile.delete(); if (!deleted) { System.err.println("⚠️ 无法删除临时文件: " + tempImageFile.getAbsolutePath()); } } // 清理子进程资源 if (process != null) { process.destroyForcibly(); } } } /** * 从图片 URL 下载保存为临时 .jpg 文件 */ private File downloadImageToFile(String imageUrl) throws IOException { URL url = new URL(imageUrl); URLConnection conn = url.openConnection(); conn.setConnectTimeout(10000); conn.setReadTimeout(30000); // 检查响应码 if (conn instanceof java.net.HttpURLConnection) { int statusCode = ((java.net.HttpURLConnection) conn).getResponseCode(); if (statusCode != 200) { throw new IOException("HTTP " + statusCode + " - 无法下载图片:" + imageUrl); } } // 创建临时文件 File tempFile = Files.createTempFile(UUID.randomUUID().toString(), ".jpg").toFile(); tempFile.deleteOnExit(); // JVM退出时自动删除 // 写入文件 try (InputStream in = conn.getInputStream(); FileOutputStream out = new FileOutputStream(tempFile)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } } return tempFile; } /** * 从输出中提取有效的 JSON 对象(忽略日志前缀) */ private JsonNode extractValidJson(String rawOutput) { try { // 尝试查找最后一个完整的 { ... } int start = rawOutput.lastIndexOf('{'); int end = rawOutput.lastIndexOf('}'); if (start != -1 && end > start) { String jsonStr = rawOutput.substring(start, end + 1); return objectMapper.readTree(jsonStr); } return objectMapper.readTree(rawOutput.trim()); } catch (JsonProcessingException e) { System.err.println("JSON解析失败:\n" + rawOutput); return null; } } private Map<String, Object> success(Object data) { Map<String, Object> map = new HashMap<>(); map.put("code", 200); map.put("msg", "success"); map.put("data", data); return map; } private Map<String, Object> error(String code, String msg) { Map<String, Object> map = new HashMap<>(); map.put("code", code); map.put("msg", msg); return map; } }注释
最新发布
10-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序媛一枚~

您的鼓励是我创作的最大动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值