Protobuf是一種輕便高效的結構化數據存儲格式,官方定義平臺無關、語言無關、可擴展、可用於通信協議和數據存儲等領域。css
它有如下優勢:html
一、平臺無關,語言無關,可擴展;前端
二、他提供了友好的動態庫,使用簡單;vue
三、解析速度快。比對應的XML快20-100倍;ios
四、序列化數據很是簡單、緊湊、與XHML相比、他的序列化以後的數據量約爲1/3到1/10.git
***先後端均可以直接在項目中使用protobuf,不用再特地額外定義model;github
***protobuf能夠直接做爲先後端數據和接口的文檔,大大減小了溝通成本;chrome
使用protobuf先後對比:vue-cli
前=> 後端語言定義的接口和字段,前端是不能直接使用的,先後端溝通是須要一份接口文檔的,一旦後端字節有變化,須要修改文檔並通知前端,文檔更新不及時會形成遺漏加大溝通成本。npm
後=> protobuf文檔是由後端統必定義的,能夠直接做爲文檔,前端只須要將protobuf文件拷貝進前端項目便可,後端字段有改動,只需通知前端更新protobuf文件便可,由於後端是直接使用了protobuf文件,它通常是不會出現遺漏或錯誤的,這樣的話團隊合做的效率會增長。
數據傳遞用json仍是protobuf其實對咱們開發人員來講沒啥大區別,protobuf最後仍是要解析成json才能用。
咱們公司的項目是基於vue開發的 一樣這個也主要是說在vue中使用。
咱們要使用protobuf.js這個庫來處理proto文件。
protobuf.js提供了幾種方法來處理proto。
~ 直接解析,如protobuf.load("awesome.proto",function(err,root) {...}
~ 轉化爲json或者js後使用,如protobuf.load("awesome.json",function(err,root){...})
~ 其餘
.proto
文件的存在,所以須要用
protobuf.js
這個庫將
*.proto
處理成
*.js
或
*.json
,而後再利用庫提供的方法來解析數據,最後獲得數據對象。
// /api/student.js 定義接口的文件 import request from '@/lib/request' // params是object類型的請求參數 // school.PBStudentListReq 是定義好的請求體model // school.PBStudentListRsp 是定義好的響應model // getStudentList 是接口名稱 export function getStudentList (params) { const req = request.create('school.PBStudentListReq', params) return request('getStudentList', req, 'school.PBStudentListRsp') } // 在HelloWorld.vue中使用 import { getStudentList } from '@/api/student' export default { name: 'HelloWorld', created () { }, methods: { _getStudentList () { const req = { limit = 20, offset = 0 } getStudentList(req).then((res) => { console.log(res) }).catch((res) => { console.error(res) }) } } }
當拿到一份定義好的proto文件時。(其實咱們前端人員不用怎麼關心proto文件,這個通常是由後端來定義和維護的,能夠直接使用下面定義好的一份demo)
// User.proto package framework; syntax = "proto3"; message PBUser { uint64 user_id = 0; string name = 1; string mobile = 2; } // Class.proto package school; syntax = "proto3"; message PBClass { uint64 classId = 0; string name = 1; } // Student.proto package school; syntax = "proto3"; import "User.proto"; import "Class.proto"; message PBStudent { uint64 studentId = 0; PBUser user = 1; PBClass class = 2; PBStudentDegree degree = 3; } enum PBStudentDegree { PRIMARY = 0; // 小學生 MIDDLE = 1; // 中學生 SENIOR = 2; // 高中生 COLLEGE = 3; // 大學生 } message PBStudentListReq { uint32 offset = 1; uint32 limit = 2; } message PBStudentListRsp { repeated PBStudent list = 1; } // MessageType.proto package framework; syntax = "proto3"; // 公共請求體 message PBMessageRequest { uint32 type = 1; // 消息類型 bytes messageData = 2; // 請求數據 uint64 timestamp = 3; // 客戶端時間戳 string version = 4; // api版本號 string token = 14; // 用戶登陸後服務器返回的 token,用於登陸校驗 } // 消息響應包 message PBMessageResponse { uint32 type = 3; // 消息類型 bytes messageData = 4; // 返回數據 uint32 resultCode = 6; // 返回的結果碼 string resultInfo = 7; // 返回的結果消息提示文本(用於錯誤提示) } // 全部的接口 enum PBMessageType { // 學生相關 getStudentList = 0; // 獲取全部學生的列表, PBStudentListReq => PBStudentListRsp }
能夠簡單的瞭解一下proto的語法。在這裏有兩種命名空間framework和school,PBStudent引用了PBUser,能夠認爲PBStudent繼承了PBUser。
通常來講,先後端須要統一約束一個請求model和響應model,好比請求中那些字段是必須的,返回體中又有那些字段,這裏用MessageType.proto
的PBMessageRequest
來定義請求體所需字段,PBMessageResponse
定義爲返回體的字段。
PBMessageType
是接口的枚舉,後端全部的接口都寫在這裏,用註釋表示具體請求參數和返回參數類型。好比這裏只定義了一個接口getStudentList
。
拿到後端提供的這份*.proto
文件後,是否是已經能夠基本瞭解到:有一個getStudentList
的接口,請求參數是PBStudentListReq
,返回的參數是PBStudentListRsp
。
說白了這個proto文件能夠直接做爲先後端溝通的文件。
步驟
新建一個vue項目
同時添加安裝axios和protobufjs。
# vue create vue-protobuf # npm install axios protobufjs --save-dev
在src目錄下新建一個proto目錄,用來存放*.proto文件,並將寫好的proto文件拷貝進去。
如今的項目目錄和package.json:
將*.proto文件生成src/proto/proto.js(***重點)
protobufjs
提供了一個叫pbjs的工具,這是一個神器,根據參數不一樣能夠打包成xx.json或xx.js文件。好比咱們想打包成json文件,在根目錄運行:
npx pbjs -t json src/proto/*.proto > src/proto/proto.json
能夠在src/proto目錄下生成一個proto.json文件。命令是:
npx pbjs -t json-module -w commonjs -o src/proto/proto.js src/proto/*.proto
-w
參數能夠指定打包js的包裝器,這裏用的是commonjs,詳情請各位本身去看文檔。運行命令後在src/proto目錄下生成的proto.js。在chrome中console.log(proto.js)
一下
:
這個模塊在原型鏈上定義了load
, lookup
等很是有用的api,這正是後面咱們將會用到的。 爲之後方便使用,咱們將命令添加到package.json的script中:
"scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint", "proto": "pbjs -t json-module -w commonjs -o src/proto/proto.js src/proto/*.proto" },
之後更新proto文件後,只須要npm run proto
便可從新生成最新的proto.js。
封裝request.js
在前面生成了proto.js文件後,就能夠開始封裝與後端交互的基礎模塊了。首先要知道,咱們這裏是用axios來發起http請求的。
整個流程:開始調用接口 -> request.js將數據變成二進制 -> 前端真正發起請求 -> 後端返回二進制的數據 -> request.js處理二進制數據 -> 得到數據對象。
能夠說request.js至關於一個加密解密的中轉站。在src/lib
目錄下添加一個request.js
文件,開始開發:
既然咱們的接口都是二進制的數據,因此須要設置axios的請求頭,使用arraybuffer,以下:
import axios from 'axios' const httpService = axios.create({ timeout: 45000, method: 'post', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/octet-stream' }, responseType: 'arraybuffer' })
MessageType.proto
裏面定義了與後端約定的接口枚舉、請求體、響應體。發起請求前須要將全部的請求轉換爲二進制,下面是request.js的主函數
import protoRoot from '@/proto/proto' import protobuf from 'protobufjs' // 請求體message const PBMessageRequest = protoRoot.lookup('framework.PBMessageRequest') // 響應體的message const PBMessageResponse = protoRoot.lookup('framework.PBMessageResponse') const apiVersion = '1.0.0' const token = 'my_token' function getMessageTypeValue(msgType) { const PBMessageType = protoRoot.lookup('framework.PBMessageType') const ret = PBMessageType.values[msgType] return ret } /** * * @param {*} msgType 接口名稱 * @param {*} requestBody 請求體參數 * @param {*} responseType 返回值 */ function request(msgType, requestBody, responseType) { // 獲得api的枚舉值 const _msgType = getMessageTypeValue(msgType) // 請求須要的數據 const reqData = { timeStamp: new Date().getTime(), type: _msgType, version: apiVersion, messageData: requestBody, token: token } } // 將對象序列化成請求體實例 const req = PBMessageRequest.create(reqData) // 調用axios發起請求 // 這裏用到axios的配置項:transformRequest和transformResponse // transformRequest 發起請求時,調用transformRequest方法,目的是將req轉換成二進制 // transformResponse 對返回的數據進行處理,目的是將二進制轉換成真正的json數據 return httpService.post('/api', req, { transformRequest, transformResponse: transformResponseFactory(responseType) }).then(({data, status}) => { // 對請求作處理 if (status !== 200) { const err = new Error('服務器異常') throw err } console.log(data) },(err) => { throw err }) } // 將請求數據encode成二進制,encode是proto.js提供的方法 function transformRequest(data) { return PBMessageRequest.encode(data).finish() } function isArrayBuffer (obj) { return Object.prototype.toString.call(obj) === '[object ArrayBuffer]' } function transformResponseFactory(responseType) { return function transformResponse(rawResponse) { // 判斷response是不是arrayBuffer if (rawResponse == null || !isArrayBuffer(rawResponse)) { return rawResponse } try { const buf = protobuf.util.newBuffer(rawResponse) // decode響應體 const decodedResponse = PBMessageResponse.decode(buf) if (decodedResponse.messageData && responseType) { const model = protoRoot.lookup(responseType) decodedResponse.messageData = model.decode(decodedResponse.messageData) } return decodedResponse } catch (err) { return err } } } // 在request下添加一個方法,方便用於處理請求參數 request.create = function (protoName, obj) { const pbConstruct = protoRoot.lookup(protoName) return pbConstruct.encode(obj).finish() } // 將模塊暴露出去 export default request
調用request.js
在.vue文件直接調用api前,咱們通常不直接使用request.js來直接發起請求,而是將全部的接口再封裝一層,由於直接使用request.js時要指定請求體,響應體等固定的值,屢次使用會形成代碼冗餘。
咱們習慣上在項目中將全部後端的接口放在src/api
的目錄下,如針對student的接口就放在src/api/student.js
文件中,方便管理。 將getStudentList
的接口寫在src/api/student.js
中
import request from '@/lib/request' // params是object類型的請求參數 // school.PBStudentListReq 是定義好的請求體model // school.PBStudentListRsp 是定義好的響應model // getStudentList 是接口名稱 export function getStudentList (params) { const req = request.create('PBStudentListReq', params) return request('getStudentList', req, 'school.PBStudentListRsp') } // 後面若是再添加接口直接以此類推 export function getStudentById (id) { // const req = ... // return request(...) }
在.vue中使用接口
須要哪一個接口,就import哪一個接口,返回的是Promise對象,很是方便。<template> <div class="hello"> <button @click="_getStudentList">獲取學生列表</button> </div> </template> <script> import { getStudentList } from '@/api/student' export default { name: 'HelloWorld', methods: { _getStudentList () { const req = { limit: 20, offset: 0 } getStudentList(req).then((res) => { console.log(res) }).catch((res) => { console.error(res) }) } }, created () { } } </script> <style lang="scss"> </style>
總結前端使用的整個流程:
src/proto
文件夾npm run proto
生成proto.jssrc/api
下寫接口.vue
文件中使用接口。(其中1和2能夠合併在一塊兒寫一個自動化的腳本,每次更新只需運行一下這個腳本便可)。
.proto
文件的,這時候能夠採起
protobuf.js
提供的其餘方法來動態解析proto,再也不須要npm run proto這種操做了。