vue中使用protobuf踩坑記

xiaosi發表於2018-06-27

因公司需求需使用protobuf,然後就有了這個踩坑記錄

什麼是protobuf?

官方解釋為:

Protocol buffers are a flexible, efficient, automated mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages. You can even update your data structure without breaking deployed programs that are compiled against the “old” format.

翻譯是(機翻—我英語不好)

協議緩衝區是用於序列化結構化資料的靈活,高效的自動化機制 – 思考XML,但更小,更快,更簡單。您可以定義一次資料的結構,然後您可以使用特殊的原始碼輕鬆地將結構化資料寫入各種資料流並使用各種語言讀取和讀取資料。您甚至可以更新您的資料結構,而不會中斷根據“舊”格式編譯的已部署程式。

特點:

  • 更簡單
  • 是3到10倍小
  • 速度要快20到100倍
  • 不太模糊
  • 生成更易於以程式設計方式使用的資料訪問類

程式碼

在github上寫了個demo
demo地址 有需要的可以下載下來跑一下就理解了。PS:如果覺得有用 請給我個小星星 (筆芯~)

使用

其實最開始我嘗試使用一個第三方JS protobuf.js
protobuf.load的時候瀏覽器報了個錯illegal token `<` (/demo.proto, line 1)查詢了下官網issue,大意應該是proto檔案多了個字元,但是我檢視過proto檔案並沒有發現有多的`<`,怎麼辦呢,最後放棄使用第三方。用官方提供的方法。

下載protobuf編譯器

下載地址 (我下載的是3.40版)
github也提供了zip包,可自行下載 (目前最新版本是v3.6.0)
用來編譯proto為JS檔案方便呼叫

配置環境變數

由於公司用的是win10 只需要將下載的檔案地址新增到path即可
Mac與window命令唯一的區別就是需要將protoc改成protoc.exe 前提是需要新增環境變數
配置環境可參考這篇文章

編寫proto檔案

為了確保前後一致,下面是後臺寫給我的一個測試proto,我司後臺是java

syntax = "proto2";//protobuf版本
option java_package = "com.test.protobuf";
option java_outer_classname = "PersonMessage";
message Person {
  required int32 id = 1;
  optional string name = 2;
  optional string email = 3;
  repeated string list = 4;
  extensions 100 to 1000;//允許擴充套件的ID
}

message PersonTree {
  optional string id = 1;
  optional string title = 2;
  repeated PersonTree childs = 3;
}

extend Person {
  optional int32 count = 101;
  optional int32 likes_cnt= 102;
}

message PersonEx {
  optional int32 id = 1;
  extend Person {
    optional int32 px = 103;
    optional int32 py= 104;
  }
  optional Person p = 2;
}

使用vue-cli構建一個工程目錄

npm install -g vue-cli
vue init webpack my-project
cd my-project
npm install
npm run dev

安裝外掛:npm install axios element-ui google-protobuf --save

編譯proto為JS

進入awesome.proto的存放路徑 使用如下命令
protoc.exe --js_out=import_style=commonjs,binary:. awesome.proto

  • 會生成一個awesome_pb.js檔案
  • 點選檢視awesome_pb.js其實可以看到裡面是生成好的方法。只需要在頁面中引入JS呼叫即可

之後我們將這個檔案引入頁面,當然你也可以考慮全域性引用

測試

本地測試

編寫一個測試頁面,建立一個測試按鈕
我是在測試頁面import messages from `./awesome_pb.js`
方法為:

methods: {
    protobufferTest () {
      var message = new messages.Person() // 呼叫Person物件  例項化
      // 賦值
      message.setId(23)
      message.setName(`asd`)
      // 序列化
      var bytes = message.serializeBinary()

      console.log(bytes) // Uint8Array(7) [8, 23, 18, 3, 97, 115, 100]

      // 反序列化
      var message2 = messages.Person.deserializeBinary(bytes)

      console.log(message2) // proto.PersonTree {wrappers_: null, messageId_: undefined, arrayIndexOffset_: -1, array: Array(3), pivot_: 1.7976931348623157e+308, …}

    }
  }

到此,本地測試完成,沒什麼毛病了。

前後端聯調測試

前方有坑

前後傳輸是使用的FormData,然後悲劇的事情來了。後臺解析不了。檢視了下資料 [8, 23, 18, 3, 97, 115, 100]確實是傳過去了。
後來排查出原因是應該是解析成了字串,然後數值變了。所以解析不出來。
後來使用fromCharCode()方法編輯成字串形式傳輸給後臺。在使用charCodeAt()取值。

此方法已棄用
protobufferTest () {
      var message = new messages.Person() // 呼叫Person物件  例項化
      // 賦值
      message.setId(23)
      message.setName(`asd`)
      // 序列化
      var bytes = message.serializeBinary()

      console.log(bytes) // Uint8Array(7) [8, 23, 18, 3, 97, 115, 100]

      var tests = ``
      for (let index = 0; index < bytes.length; index++) {
        tests += String.fromCharCode(bytes[index])
      }
      console.log(tests) // asd

      // 存入FormData
      let uploadDatas = new FormData()
      uploadDatas.append(`protobuf`, tests)

      // 使用axios傳輸給後臺
      this.axios.post(`/test`, uploadDatas)
        .then(function (response) {
          // 將傳回的字串轉為陣列
          console.log(response.data.split(``)) // ["↵", "", "3", "2", "", "", "a", "s", "d", "f"]
          let str = response.data.split(``)
          let toChar = []
          for (let index = 0; index < str.length; index++) {
            toChar.push(str[index].charCodeAt())
          }
          console.log(toChar) // [10, 2, 51, 50, 18, 4, 97, 115, 100, 102]

          // 後臺傳回來的是PersonTree裡面的值所以呼叫PersonTree來反序列化
          var message2 = messages.PersonTree.deserializeBinary(toChar)

          console.log(message2) // proto.PersonTree {wrappers_: null, messageId_: undefined, arrayIndexOffset_: -1, array: Array(3), pivot_: 1.7976931348623157e+308, …}

          // 獲取PersonTree的id值
          console.log(message2.getId()) // 32
        })
        .catch(function (error) {
          console.log(error)
        })

    }

以上方法可能存在安全隱患。
向後端傳值
因為FormData支援兩種方式傳輸string和blob所以將bytes存入blob中
前端獲取資料
對axios的預設傳輸方式做個更改
axios.defaults.responseType = `arraybuffer`
將以上的JS程式碼更改為以下內容

protobufferTest () {
      var message = new messages.Person()
      message.setId(23)
      message.setName(`asd`)
      var bytes = message.serializeBinary()
      
      console.log(bytes)
      let uploadDatas = new FormData()
      var blob = new Blob([bytes], {type: `application/octet-stream`})

      uploadDatas.append(`protobuf`, blob)
      
      this.axios.post(`/test`, uploadDatas)
        .then(function (response) {
          console.log(response)

          var message2 = messages.PersonTree.deserializeBinary(response.data)
          console.log(message2.getId())
        })
        .catch(function (error) {
          console.log(error)
        })
      // console.log(bytes)
    }

至此前後聯調完成

參考文件

前端後臺以及遊戲中使用google-protobuf詳解
javascript前端如何使用google-protobuf

相關文章