前端压缩proto文件.json

616 阅读3分钟

前端如何封装protobuf进行数据交互,我已经在这里面写清楚了,可以先看这篇文章.更容易懂我本篇文章 前端protobuf请求响应封装(axios)

但是在开发过程中,会发现一个问题,比如在使用后端给我们的proto的文件的时候,其实很多接口有可能是废弃的,或者说是其他活动的, 枚举是多余的,等等,总之会出现这种问题.

对于我们用不上的proto,我们依然通过指令将所有的proto文件转化成了json,实际上我们只需要保留我们的api用到了的接口,所以存在一个json文件的压缩操作.

1.场景问题

我拿时间戳接口,奖励记录接口进行测试 以下是这两个接口完整的proto文件:

1666158465488.png

通过protobufjs-cli进行转化成json

"scripts": {
    "proto": "npx pbjs -t json src/protobuf/protoFile/*.proto > src/protobuf/protoFile/proto.json", 
},

1666158976526.png

这时候proto.json的占用大小为3.21kb

e8c9b61498862f3225230efcb285b8a.png

引入proto.json之后,我们能到拿到这两个接口的数据

1666159908693.png

1666159806486.png

假设我这个活动不需要调用时间戳了,只需要调用奖励记录接口,那么我们应该根据我们需要的api,得出最简化的json,再引入,肯定不可能手动去剔除不要的proto文件或者里面的内容,因为一个package里面包含很多的接口

2.实现过程

2.1 配置运行指令
"scripts": {
    "compressProto": "node src/protobuf/compressProto/index" 
 },
2.2 1 下载包
npm i protobufjs-cli colors --save
2.3 代码
const colors = require("colors");
const path = require("path")
// 1.首先应该对pb进行压缩-开启子进程
const { execSync } = require('child_process')
execSync(`npx pbjs -t json src/protobuf/protoFile/*.proto > src/protobuf/protoFile/proto.json`, {
  encoding: "utf8",
})

// 2.拿到所有的pb转化的json数据
const allProtoJson = require('../protoFile/proto.json')

// 3. 拿配置的接口与全部的json进行匹配,主要是根据名字来匹配,只保留当前的需要的,进行压缩
const projectCurrentApiJson = require('./api/conf.json').api
const commonPath = path.resolve(__dirname, '../');

const { minifyPb } = require("./minify");
minifyPb(
  projectCurrentApiJson,
  allProtoJson,
  `${commonPath}/protoFile/index.min.json`
)

console.log(colors.green("pb压缩成功, 生成index.min.json"));

其实要理解的是转化之后json的结构是什么样,除了固定的api名+req和api+res,其他的非基础类型的对象和他们都是同级的,所以逻辑是:

1.先找到allProtoJson里面所有关于接口请求有关的key-requestResponseKeys这里我们用xxx进行匹配.就能拿到allProtoJson所有的api对应的req名字和res名字.

2.通过const methodsInfo = nested[ajaxKey]["methods"][subMethods.name],也就是nested底下一个个去判断requestResponseKeys里面的元素底下的req和res是否存在,如果存在就是表示这个api是被需要的,并且赋值给新创建的newNested

 newNested[ajaxKey] = newNested[ajaxKey] || { methods: {} };
 newNested[ajaxKey]["methods"][subMethods.name] = methodsInfo;

3.用types来存newNested所有的api名+req和api+res的同级key,目前只拿到了接口的res和req

const { requestType, responseType } = methodsInfo
const ajaxArr = [requestType, responseType]
// 2.1 types里面加入item
ajaxArr.forEach(item => {
  if(!types.includes(item)) {
    types.push(item)
  }
})

4.addTypesToNested用来将api的req和res里面的非基础类型的添加到json上

function addTypesToNested(nested, newNested, types) {
  // 1.得到的types应该还要加上服务端统一的请求
  types = types.concat(commonTypes);
  for (let i = 0; i < types.length; i++) {
    // 2.如果nested[types[i]]存在但是newNested[types[i]不存在,就进行赋值
    if (!newNested[types[i]] && nested[types[i]]) {
      newNested[types[i]] = nested[types[i]];
      // 3.判断fileds里面是不是基础类型,如果是,就不用管,不是就作为types中的 Enum类型没有fileds
      let fields = nested[types[i]].fields || {};
      Object.keys(fields).forEach((key) => {
        // 如果不是基本类型,就是可以需要被单独弄成属性的, types要新增
        if (!primitiveTypes.includes(fields[key].type)) {
          types.push(fields[key].type); // 新增后,引用类型可以新增循环
        }
      });
    }
  }
}

1666162455483.png

1666162511472.png

代码如下: conf.json

{
  "api": [
    {
      "remark": "活动奖励明细",
      "name": "RechargeBonusList"
    }
  ]
}
const fs = require("fs")

// 原始类型 不需要递归(不需要作为key)
const primitiveTypes = [
  "int32",
  "int64",
  "sint32",
  "sint64",
  "uint32",
  "uint64",
  "bytes",
  "string",
  "bool",
];

// 保存压缩后的代码
function saveMiniJson(data, filepath) {
  const str = JSON.stringify(data,"","\t") // 格式化成字符串
  fs.writeFile(filepath, str, { 'flag': 'a' }, function(err) {
    if (err) {
      throw err;
    }
    // 写入成功后读取测试
    fs.readFile(filepath, 'utf-8', function(err, data) {
      if (err) {
        throw err;
      }
      console.log(`index.min.json已经写入啦`)
    });
  });
}

/**
 * 给nested添加方法
 * @param {*} nested
 * @param {*} newNested
 * @param {Array} methods
 * @return [types]  方法用到的类型
 */
 function addFunctionToNested(nested, newNested, methods) {
  // 方法需要的types,也就是对应的json里面package的nest底下的key,
   //首先需要确定的是所需要的方法是从方法名从而拿到请求
   // 和相应需要的所有的type,而含属于请求的可以通过固定的ExtObj进行匹配到
  // 1.拿到与请求响应有关的key [ 'ActivityExtObj', 'IndexExtObj' ]
  const requestResponseKeys =  Object.keys(nested).filter((key) => {
    return /xxx$/i.test(key)
  })
  // 2.api中含有的请求响应的key, methods是一定在requestResponseKeys里面的,
  挑出methodsInfo存在的数据
  const types = [] // [ 'GetTimestampReq', 'GetTimestampRes' ]
  requestResponseKeys.forEach(ajaxKey => {
    methods.forEach(subMethods => {
      const methodsInfo = nested[ajaxKey]["methods"][subMethods.name]
      // 如果存在数据
      if (methodsInfo) {
        const { requestType, responseType } = methodsInfo
        const ajaxArr = [requestType, responseType]
        // 2.1 types里面加入item
        ajaxArr.forEach(item => {
          if(!types.includes(item)) {
            types.push(item)
          }
        })
        // 2.2存在的就给newNested加上
        // 给新的newNested添加上, 存在的api上的方法
        newNested[ajaxKey] = newNested[ajaxKey] || { methods: {} };
        newNested[ajaxKey]["methods"][subMethods.name] = methodsInfo;
      }
    })
  })
  return types;
}

const commonTypes = ["xxxOutxxx", "xxxInxxx"];
// 添加消息体
function addTypesToNested(nested, newNested, types) {
  // 1.得到的types应该还要加上服务端统一的请求
  types = types.concat(commonTypes);
  for (let i = 0; i < types.length; i++) {
    // 2.如果nested[types[i]]存在但是newNested[types[i]不存在,就进行赋值
    if (!newNested[types[i]] && nested[types[i]]) {
      newNested[types[i]] = nested[types[i]];
      // 3.判断fileds里面是不是基础类型,如果是,就不用管,不是就作为types中的 Enum类型没有fileds
      let fields = nested[types[i]].fields || {};
      Object.keys(fields).forEach((key) => {
        // 如果不是基本类型,就是可以需要被单独弄成属性的, types要新增
        if (!primitiveTypes.includes(fields[key].type)) {
          types.push(fields[key].type); // 新增后,引用类型可以新增循环
        }
      });
    }
  }
}
  
/**
 * 打包压缩pb
 * @param {*} methods  所需方法配置
 * @param {*} pbJson   原有pb
 * @param {*} savePath 保存路径
 */
function minifyPb(methods, pbJson, savePath) {
  const formatMethods = methods
  // 1.拿到所有的package ,也就是json的第一层key .类似于pb, gm_pb等
  const pbs = Object.keys(pbJson.nested); // [ 'pb' ]  拿到所有的packages
  // 2.创建空json对象来创建
  let newNested = {};
  pbs.forEach((pb) => {
    // 2.1 为每个package创建新的对象
    newNested[pb] = { nested: {} };
    const nested = pbJson.nested[pb].nested; // 每个package底下所有的用到的方法
    // 2.2 为每个package添加必要的方法 [ 'GetTimestampReq', 'GetTimestampRes' ]
    let needTypes = addFunctionToNested(
      nested,
      newNested[pb].nested,
      formatMethods
    );
    // 添加消息体
    addTypesToNested(nested, newNested[pb].nested, needTypes);
  });

  // 保存JSON
  saveMiniJson(Object.assign(pbJson, { nested: newNested }), savePath)
}

module.exports = {
  minifyPb,
};

3结果

1666162776158.png

index.min.json中我们将10项压缩成了6项,大小也变了

dc16510ac25c2f6108f001ab1843172.png

也可以直接压缩成无json格式字符串:

// const str = JSON.stringify(data,"","\t") // 格式化成字符串
  const str = JSON.stringify(data) // 格式化成字符串

1666163201710.png

修改上篇文章引入方式:

import protoJson from '../protoFile/index.min.json'

1666163426081.png