Visit 项目维护 Protobuf 文件

豆子碎片小程序在版本 5.0 开始使用 Protobuf 传输和存储数据。在使用 Protobuf 前,使用的是 JSON 数据,原始 JSON 数据文件大小为 10KB,在生成 PB 文件后,PB 文件只有 4KB。本文章主要记录如何维护 Protobuf 数据,和开发中碰到的问题记录。

现在由于实际需求和维护原因,又切回使用 JSON 文件。

目前是使用 JSON 文件维护原数据,后期可以考虑使用数据库来存储。将 JSON 文件转为 PB 文件的工具是使用 Python 写的脚本。

下面开始一步一步介绍,直到 PB 文件可以使用。

一、首先定义 data.proto 文件,这个文件 Python 和 JavaScript 都需要用到。

syntax = "proto3";

message DataItem {
  string aid = 1;
  string name = 2;
  string category = 3;
  string label = 4;
  int32 grade = 5;
}

message DataList {
  repeated DataItem items = 1;
}

上面的内容就是描述了一个数组对象。

二、使用 Python 将原数据转成 Protobuf 文件。

需要使用 protoc 将 proto 文件编译为 data_pb2.py。你如果首次使用,需要安装 protoc 和使用 pip 安装 protobuf 包。更详细的内容可以参考【将 JSON 文件转换为 Protobuf 格式】文章。

使用的命令如下:

protoc --python_out=. data.proto

在生成该文件后,就可以使用下面的脚本将 data.json 文件编译为 data.bin 文件。Python 脚本 json2pb.py 源代码如下:

import json
from data_pb2 import DataList

def json_to_protobuf(json_path, proto_path):
    # 读取 JSON 数据
    with open(json_path, "r", encoding="utf-8") as f:
        json_data = json.load(f)

    # 创建 Protobuf 对象
    proto_data = DataList()

    # 填充数据
    for item in json_data:
        proto_item = proto_data.items.add()
        proto_item.aid = item.get("id", "")
        proto_item.name = item.get("name", "")
        proto_item.category = item.get("category", "")
        proto_item.label = item.get("label", "")
        proto_item.grade = item.get("grade", 1)  # 默认值 1

    # 序列化并保存
    with open(proto_path, "wb") as f:
        f.write(proto_data.SerializeToString())
    print(f"转换完成!输出文件: {proto_path}")

if __name__ == "__main__":
    json_to_protobuf("data.json", "data.bin")

需要注意的是,data_pb2.py 文件一定要和脚本文件放在一起,执行才不会报错。文件写入模式也要使用 wb 模式,否则在下一步 Js 解析时会出现异常。

三、使用 JavaScript 解析 PB 文件并使用。

首先需要将 data.bin 文件放到 Web 服务器,方便小程序下载文件。然后安装 Nodejs 和 npm,并安装包 protobufjs 版本 6.11.3

安装命令如下:

npm install -g protbufjs@6.11.3

将 protobufjs 应用到小程序花费了很多时间,主要是将 protobufjs.min.js 拷贝到小程序项目后,小程序一直报 protobuf load is not a function。我使用的源码如下:

 // 1. 下载并读取二进制文件
      const arrayBuffer = await this.getFileBuffer('https://example.com/data.bin');

      // 2. 加载 Proto 定义(精简版)
      const root = await protobuf.load({
        nested: {
          DataArray: {
            fields: {
              items: {
                rule: 'repeated',
                type: 'DataItem',
                id: 1
              }
            }
          },
          DataItem: {
            fields: {
              id: { type: 'string', id: 1 },
              name: { type: 'string', id: 2 },
              kw: { type: 'string', id: 3 },
              lock: { type: 'bool', id: 4 },
              category: { type: 'string', id: 5 },
              label: { type: 'string', id: 6 },
              grade: { type: 'int32', id: 7 }
            }
          }
        }
      });

      // 3. 创建解码器
      const DataArray = root.lookupType("DataArray");

      // 4. 执行转换(核心操作)
      const decoded = DataArray.decode(new Uint8Array(arrayBuffer));

各种找答案排查问题,刚开始下载的 protobufjs 是 7 版本,网上说这个版本有问题,又使用 6 版本,还是报那个错误,最后一个帖子说是微信小程序有限制,不能动态加载函数,需要先将 proto 文件转为 JS 文件。

下面的内容就是将 proto 文件转为 js 文件,参考网上的帖子,这个命令我运行一直报错。

pbjs -t static-module -w commonjs -o datalist.js *.proto

我使用的 Windows 操作系统,提示没有这个命令。然后一番查找后,使用这个命令可以运行:

npx pbjs -t static-module -w commonjs -o datalist.js *.proto

记住,需要在 proto 文件夹的目录运行。

执行命令后,会生成 datalist.js 文件,我们需要将 datalist.js 和 protobuf.min.js 文件一块拷贝到小程序项目目录下。我这里是放入到 libs 目录下。

在拷贝完成后,需要修改 datalist.js 文件,将 protobufjs 引入文件地址修改下,例如改成下面这样的。

var $protobuf = require("./protobuf.min.js");

现在我们还需要一个 JS 文件来解码 Protobuf bin 文件,并转换数据。
这个文件可以写成一个工具文件,我的 js 文件内容如下:

const pb = require('./datalist.js');
function loadAndDecodeProtoFile(){
	const filePath = `${wx.env.USER_DATA_PATH}/data.bin`;
	const fs = wx.getFileSystemManager();
    // 同步接口
    try {
	  const res = fs.readFileSync(filePath);
	  const decoded = pb.DataList.decode(new Uint8Array(res));
	  const result = decoded.items.map(item => ({
		  aid: item.aid || '',
		  name: item.name || '',
		  category: item.category || '',
		  label: item.label || '',
		  grade: Number(item.grade) || 1 // 确保数字类型
	  }));
	  return result;
    } catch (e) {
	  console.error('文件读取或解码失败:',e)
    }
}

module.exports = {
	loadAndDecodeProtoFile
}

在这里,我碰到一个大坑,花费了很多时间进行排错,现象就是返回的数组是空的。错误的代码如下:

  const res = fs.readFileSync(filePath,'binary');

网上教程说需要添加’binary’二进制格式,我排查了很久才发现去掉之后就正常了。

如果你从网上下载的 data.bin 文件需要写入到小程序本地文件,则一定要使用’binary’格式,保存文件代码如下:

  // 写入Bin文件
  writeBinFile: function (data) {
    const that = this;
    const filePath = `${wx.env.USER_DATA_PATH}/data.bin`;
    const fs = wx.getFileSystemManager();
    // 会覆盖写入
    fs.writeFile({
      filePath: filePath,
      data: data,
      encoding: 'binary',
      success: res => {
        console.log('write file success', res)
      },
      fail: err => {
        console.error('write bin file error,', err)
      }
    })
  },

最后就是在小程序中真正使用 Protobuf 数据了。调用方法可以这样写:

const pbdata = require('./libs/pbdata.js');

 try {
      const dataList = pbdata.loadAndDecodeProtoFile();
      if(utils.isEmpty(dataList)){
        console.log('出现问题了');
      }else{
         console.log('数据解析出来了,', dataList);
      }

    } catch (err) {
      log.error('获取bin文件err,', err);
    }

终于完成了,从制作到 Protobuf 文件到使用 Protbuf 文件。使用 Protobuf 的好处就是可以多种语言使用,网络传输和存储相比 JSON 更小更快。