axios使用protobuf进行通讯

3,863 阅读4分钟

proto通讯资料 github.com/protocolbuf… github.com/dcodeIO/pro…

或者你可以直接使用作者封装好proto通讯的开发脚手架 地址github.com/oujin-nb/vu…

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式

首先我们之前使用Json进行通讯是使用文本进行通讯,而protobuf是使用二进制通讯,通讯效率可以见下图

在这里插入图片描述

这里是介绍在es6前端模块化项目中如何简单高效的使用protobuf进行通讯

首先理清思路:1 将通用的.proto文件解析生成前端能够使用的js文件 2将普通的js对象引用protobuf提供的方法序列化成指定的二进制数据 3 将后端传来的数据解析成js对象

步骤

1 解析.proto文件 准备一个文件夹专门来放.proto文件

在这里插入图片描述
然后新增指令执行新建的文件夹

  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "build": "node build/build.js",
    "proto": "pbjs -t json-module -w commonjs -o src/configFile/proto/proto.js  src/configFile/proto/*.proto"
  },

直接执行 npm run proto
便会生成proto.js文件

2 封装proto请求

一个完整的请求如下 首先看后台给我们的proto文件 BaseResponse


syntax = "proto3";
option csharp_namespace = "probofu.Controllers";
import "google/protobuf/any.proto";

message BaseResponse {
  bool IsSuccess = 1;
  string Message = 2;
  string Token = 3;
  google.protobuf.Any data = 4;
}


Person


syntax = "proto3";
option csharp_namespace = "probofu.Controllers";

message Person {
  int32 Id = 1;
  string Name = 2;
  Address  Address=3;
}
message Address {
  string Line1 = 1;
  string Line2 = 2;
}







引用生成的js文件发送请求如下

  requestTest1() {
      let person = require("@/configFile/proto/proto");
      let protobufRoot = require("protobufjs").Root;
      let root = protobufRoot.fromJSON(person);
      let userInfo = root.lookupType("Person");
      let BaseResponse = root.lookupType("BaseResponse");
      let infoData = {Name:'xiaoming',Id:24};
      // 将js对象序列化成二进制
      let infoEncodeMessage = userInfo
        .encode(userInfo.create(infoData))
        .finish();
      let blob = new Blob([infoEncodeMessage], {type: 'buffer'});
      // 新建一个axios对象
      const httpService = axios.create({
        timeout: 45000,
        method: "post",
        headers: {
          "X-Requested-With": "XMLHttpRequest",
          "Content-Type": "application/octet-stream"
        },
        responseType: "arraybuffer"
      });

    
      httpService
        .post(
          "http://192.168.1.31:5000/api/system/getsth",
        blob
        )
        .then(e => {
          // 将二进制数据生成js对象
          const buf = protobuf.util.newBuffer(e.data);
          let res = BaseResponse.decode(buf);
          let person = userInfo.decode(res.data.value);
        });
    },

但是在实际开发中我们不能每次都这样发送请求应该封装一层,通过直接传入请求参数模板和返参解析模板,做到传入js对象请求完成后返回js对象,而通讯的时候用protobuf进行通讯

先准备一份配置文件

export default [
   { test:{url:'system/getsth',requestTmp:'Person',responseTmp:'Person'}},
]

补充配置文件一般是按照接口文档往里面写,但是实际开发中往往有几百个接口,写起来来繁琐而且容易出错,我这边的处理建议是让后台将接口文档写成固定格式的excel然后我们前端直接解析excel生成js配置对象,这样既方便又能甩锅

方法如下(注意异步处理):

import axios from 'axios'
import XLSX from 'xlsx'

async function  getConfigList(){
    let interfaceList = []
    // 读取本地excel文件
    let x = await  axios({
        url: "../../static/file/proto接口文档.xlsx",
        method: 'get',
        responseType:'arraybuffer'
      })
      var data = new Uint8Array(x.data);
      var arr = new Array();
      for(var i = 0; i != data.length; ++i) arr[i] = String.fromCharCode(data[i]);
      var bstr = arr.join("");
      var workbook = XLSX.read(bstr, {type:"binary"});
      workbook.SheetNames.forEach(y=>{
          let jsonSheet = XLSX.utils.sheet_to_json(workbook.Sheets[y])
          if(jsonSheet.length>0){
            jsonSheet.forEach(z=>{
                let interfaceObj={}
                interfaceObj[z['路由']+z['方法名']]={
                    url:z['路由']+'/'+z['方法名'],
                    requestTmp:z['参数Proto文件'],
                    responseTmp:z['返回响应Proto']
                }
                interfaceList.push(interfaceObj)
            })
          }
      })
      return interfaceList
}
export default getConfigList


引入配置文件生成配置好可用作proto通讯的axios对象并挂载在vue的原型上

import protoRoot from "@/configFile/proto/proto"
import protobuf from 'protobufjs'
import axios from 'axios'
import apiConfig from './protoApi/index'

// 基础response模板
let BaseResponse = protoRoot.lookupType("BaseResponse");

const createRequest = (option) => {
  return axios.create({
    timeout: 10000,
    method: "post",
    headers: {
      "X-Requested-With": "XMLHttpRequest",
      "Content-Type": "application/octet-stream",
      'token': localStorage.getItem("token")
    },
    baseURL: process.env.NODE_ENV == 'development' ? process.env.API_HOST : HOST,
    responseType: "arraybuffer"
  });
}
const getApiInstance = (option) => {
  console.log(option)
  // 根据参数配置请求模板和解析模板
   let requetProto = protoRoot.lookupType(option.requestTmp);
   let responseProto = protoRoot.lookupType(option.responseTmp);
  let api = createRequest()
  api.interceptors.request.use(
    config => {
      config.url = option.url;
      let data = Object.assign({},config.data)
      config.data = new Blob([requetProto.encode(requetProto.create(data)).finish()], { type: 'buffer' });
      return config;
    },
    error => {
      return Promise.reject(error);
    }
  );
  api.interceptors.response.use(
    response => {
      const buf = protobuf.util.newBuffer(response.data);
      let res = BaseResponse.decode(buf);
      let resData = responseProto.decode(res.data.value);
      return resData
    },
    error => {
    }
  );

  return api
}

/* 
如果采用excel生成js配置文件
*/
// const getApiMap = async () => {
//   let apiList = {}
//   let d = await apiConfig()
//   d.forEach((s) => {
//     let key = Object.keys(s)[0]
//     let val = s[key]
//     apiList[key] = getApiInstance(val)
//   })
//   return apiList
// }
/* 
如果是手写js配置文件
*/
const getApiMap = ()=>{
  let apiList = {}
  apiConfig.forEach((s)=>{
    let key = Object.keys(s)[0]
    let val = s[key]
    apiList[key]= getApiInstance(val)
  })
  return apiList
}


getApiMap()

export default getApiMap()

挂载在vue的原型上

注意:如果你是采用读取excel生成配置文件,那么在main方法你将得到一个promise对象(async方法返回一个primise对象)所以这里我们需要做同步处理

import api from '../src/config/protoReqConfig'

Vue.prototype.api = api

// excel文件生成配置文件
function creatVue(){
	new Vue({
        el: '#app',
        router,
        store,
        components: {App},
        template: '<App/>'
    })
}
console.log(api)
api.then(x=>{
    Vue.prototype.api =  x // proto格式http请求
	creatVue()
}).catch(x=>{
	console.log('创建api对象失败')
	console.log(x)
    creatVue()
})

3 实际应用 直接调用之前配置好的方法即可

  this.api.test({data:{Name:'daming',Id:25}}).then((s)=>{
        console.log(s)
      })

github github.com/oujin-nb/vu…