国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

vue中使用protobuf的過程記錄

2019-11-19 12:37:44
字體:
供稿:網(wǎng)友

由于目前公司采用了ProtoBuf做前后端數(shù)據(jù)交互,進(jìn)公司以來一直用的是公司大神寫好的基礎(chǔ)庫,完全不了解底層是如何解析的,一旦報錯只能求人,作為一只還算有鉆研精神的猿,應(yīng)該去了解一下底層的實現(xiàn),在這里記錄一下學(xué)習(xí)過程。

Protobuf簡單介紹

Google Protocol Buffer(簡稱 Protobuf)是一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式,平臺無關(guān)、語言無關(guān)、可擴展,可用于通訊協(xié)議和數(shù)據(jù)存儲等領(lǐng)域。

有幾個優(yōu)點:

1.平臺無關(guān),語言無關(guān),可擴展;
2.提供了友好的動態(tài)庫,使用簡單;
3.解析速度快,比對應(yīng)的XML快約20-100倍;
4.序列化數(shù)據(jù)非常簡潔、緊湊,與XML相比,其序列化之后的數(shù)據(jù)量約為1/3到1/10。

個人感受: 前后端數(shù)據(jù)傳輸用json還是protobuf其實對開發(fā)來說沒啥區(qū)別,protobuf最后還是要解析成json才能用。個人覺得比較好的幾點是:

1.前后端都可以直接在項目中使用protobuf,不用再額外去定義model;
2.protobuf可以直接作為前后端數(shù)據(jù)和接口的文檔,大大減少了溝通成本;

沒有使用protobuf之前,后端語言定義的接口和字段,前端是不能直接使用的,前后端溝通往往需要維護(hù)一份接口文檔,如果后端字段有改動,需要去修改文檔并通知前端,有時候文檔更新不及時或容易遺漏,溝通成本比較大。 使用protobuf后,protobuf文件由后端統(tǒng)一定義, protobuf直接可以作為文檔 ,前端只需將protobuf文件拷貝進(jìn)前端項目即可。如果后端字段有改動,只需通知前端更新protobuf文件即可,因為后端是直接使用了protobuf文件,因此protobuf文件一般是不會出現(xiàn)遺漏或錯誤的。長此以往,團隊合作效率提升是明顯的。

廢話了一大堆,下面進(jìn)入正題。 我這里講的主要是在vue中的使用,是目前本人所在的公司項目實踐,大家可以當(dāng)做參考。

思路

前端中需要使用 protobuf.js 這個庫來處理proto文件。

protobuf.js 提供了幾種方式來處理proto。

protobuf.load("awesome.proto", function(err, root) {...})protobuf.load("awesome.json", function(err, root) {...})

眾所周知,vue項目build后生成的dist目錄中只有html,css,js,images等資源,并不會有 .proto 文件的存在,因此需要用 protobuf.js 這個庫將 *.proto 處理成 *.js 或 *.json ,然后再利用庫提供的方法來解析數(shù)據(jù),最后得到數(shù)據(jù)對象。

PS: 實踐發(fā)現(xiàn),轉(zhuǎn)化為js文件會更好用一些,轉(zhuǎn)化后的js文件直接在原型鏈上定義了一些方法,非常方便。因此后面將會是使用這種方法來解析proto。

預(yù)期目標(biāo)

在項目中封裝一個 request.js 模塊,希望能像下面這樣使用,調(diào)用api時只需指定請求和響應(yīng)的model,然后傳遞請求參數(shù),不需關(guān)心底層是如何解析proto的,api返回一個Promise對象:

// /api/student.js 定義接口的文件import request from '@/lib/request'// params是object類型的請求參數(shù)// school.PBStudentListReq 是定義好的請求體model// school.PBStudentListRsp 是定義好的響應(yīng)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)  }) } }}

準(zhǔn)備工作

1.拿到一份定義好的proto文件。

雖然語法簡單,但其實前端不用怎么關(guān)心如何寫proto文件,一般都是由后端來定義和維護(hù)。在這里大家可以直接用一下我定義好的一份 demo 。

// User.protopackage framework;syntax = "proto3";message PBUser { uint64 user_id = 0; string name = 1; string mobile = 2;}// Class.protopackage school;syntax = "proto3";message PBClass { uint64 classId = 0; string name = 1;}// Student.protopackage 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; // 小學(xué)生 MIDDLE = 1; // 中學(xué)生 SENIOR = 2; // 高中生 COLLEGE = 3; // 大學(xué)生}message PBStudentListReq { uint32 offset = 1; uint32 limit = 2;}message PBStudentListRsp { repeated PBStudent list = 1;}// MessageType.protopackage framework;syntax = "proto3";// 公共請求體message PBMessageRequest { uint32 type = 1;       // 消息類型 bytes messageData = 2;      // 請求數(shù)據(jù) uint64 timestamp = 3;      // 客戶端時間戳 string version = 4;       // api版本號 string token = 14;       // 用戶登錄后服務(wù)器返回的 token,用于登錄校驗}// 消息響應(yīng)包message PBMessageResponse { uint32 type = 3;       // 消息類型 bytes messageData = 4;      // 返回數(shù)據(jù) uint32 resultCode = 6;      // 返回的結(jié)果碼 string resultInfo = 7;      // 返回的結(jié)果消息提示文本(用于錯誤提示)}// 所有的接口enum PBMessageType { // 學(xué)生相關(guān) getStudentList = 0;       // 獲取所有學(xué)生的列表, PBStudentListReq => PBStudentListRsp}

其實不用去學(xué)習(xí)proto的語法都能一目了然。這里有兩種命名空間 framework 和 school , PBStudent 引用了 PBUser ,可以認(rèn)為 PBStudent 繼承了 PBUser 。

一般來說,前后端需要統(tǒng)一約束一個請求model和響應(yīng)model,比如請求中哪些字段是必須的,返回體中又有哪些字段,這里用 MessageType.proto 的 PBMessageRequest 來定義請求體所需字段, PBMessageResponse 定義為返回體的字段。

PBMessageType 是接口的枚舉,后端所有的接口都寫在這里,用注釋表示具體請求參數(shù)和返回參數(shù)類型。比如這里只定義了一個接口 getStudentList 。

拿到后端提供的這份 *.proto 文件后,是不是已經(jīng)可以基本了解到:有一個 getStudentList 的接口,請求參數(shù)是 PBStudentListReq ,返回的參數(shù)是 PBStudentListRsp 。

所以說proto文件可以直接作為前后端溝通的文檔。

步驟

1.新建一個vue項目

同時添加安裝 axios 和 protobufjs 。

# vue create vue-protobuf# npm install axios protobufjs --save-dev

2.在 src 目錄下新建一個 proto 目錄,用來存放 *.proto 文件,并將寫好的proto文件拷貝進(jìn)去。

此時的項目目錄和 package.json :

3.將 *.proto 文件生成 src/proto/proto.js (重點)

protobufjs 提供了一個叫 pbjs 的工具,這是一個神器,根據(jù)參數(shù)不同可以打包成xx.json或xx.js文件。比如我們想打包成json文件,在根目錄運行:

npx pbjs -t json src/proto/*.proto > src/proto/proto.json

可以在 src/proto 目錄下生成一個proto.json文件,查看請點擊這里。 之前說了:實踐證明打包成js模塊才是最好用的。我這里直接給出最終的命令

npx pbjs -t json-module -w commonjs -o src/proto/proto.js src/proto/*.proto

-w 參數(shù)可以指定打包js的包裝器,這里用的是commonjs,詳情請各位自己去看文檔。運行命令后在src/proto目錄下生成的 proto.js 。在chrome中 console.log(proto.js) 一下:

可以發(fā)現(xiàn),這個模塊在原型鏈上定義了 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。

4. 封裝request.js

在前面生成了proto.js文件后,就可以開始封裝與后端交互的基礎(chǔ)模塊了。首先要知道,我們這里是用axios來發(fā)起http請求的。

整個流程:開始調(diào)用接口 -> request.js將數(shù)據(jù)變成二進(jìn)制 -> 前端真正發(fā)起請求 -> 后端返回二進(jìn)制的數(shù)據(jù) -> request.js處理二進(jìn)制數(shù)據(jù) -> 獲得數(shù)據(jù)對象。

可以說request.js相當(dāng)于一個加密解密的中轉(zhuǎn)站。在 src/lib 目錄下添加一個 request.js 文件,開始開發(fā):

既然我們的接口都是二進(jìn)制的數(shù)據(jù),所以需要設(shè)置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 里面定義了與后端約定的接口枚舉、請求體、響應(yīng)體。發(fā)起請求前需要將所有的請求轉(zhuǎn)換為二進(jìn)制,下面是request.js的主函數(shù)

import protoRoot from '@/proto/proto'import protobuf from 'protobufjs'// 請求體messageconst PBMessageRequest = protoRoot.lookup('framework.PBMessageRequest')// 響應(yīng)體的messageconst 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 請求體參數(shù) * @param {*} responseType 返回值 */function request(msgType, requestBody, responseType) {  // 得到api的枚舉值 const _msgType = getMessageTypeValue(msgType) // 請求需要的數(shù)據(jù) const reqData = { timeStamp: new Date().getTime(), type: _msgType, version: apiVersion, messageData: requestBody, token: token }} // 將對象序列化成請求體實例 const req = PBMessageRequest.create(reqData) // 調(diào)用axios發(fā)起請求 // 這里用到axios的配置項:transformRequest和transformResponse // transformRequest 發(fā)起請求時,調(diào)用transformRequest方法,目的是將req轉(zhuǎn)換成二進(jìn)制 // transformResponse 對返回的數(shù)據(jù)進(jìn)行處理,目的是將二進(jìn)制轉(zhuǎn)換成真正的json數(shù)據(jù) return httpService.post('/api', req, { transformRequest, transformResponse: transformResponseFactory(responseType) }).then(({data, status}) => { // 對請求做處理 if (status !== 200) {  const err = new Error('服務(wù)器異常')  throw err } console.log(data) },(err) => { throw err })}// 將請求數(shù)據(jù)encode成二進(jìn)制,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響應(yīng)體  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下添加一個方法,方便用于處理請求參數(shù)request.create = function (protoName, obj) { const pbConstruct = protoRoot.lookup(protoName) return pbConstruct.encode(obj).finish()}// 將模塊暴露出去export default request

最后寫好的具體代碼請看: request.js 。 其中用到了 lookup() , encode() , finish() , decode() 等幾個proto.js提供的方法。

5. 調(diào)用request.js

在.vue文件直接調(diào)用api前,我們一般不直接使用request.js來直接發(fā)起請求,而是將所有的接口再封裝一層,因為直接使用request.js時要指定請求體,響應(yīng)體等固定的值,多次使用會造成代碼冗余。

我們習(xí)慣上在項目中將所有后端的接口放在 src/api 的目錄下,如針對student的接口就放在 src/api/student.js 文件中,方便管理。 將 getStudentList 的接口寫在 src/api/student.js 中

import request from '@/lib/request'// params是object類型的請求參數(shù)// school.PBStudentListReq 是定義好的請求體model// school.PBStudentListRsp 是定義好的響應(yīng)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(...)}

6. 在.vue中使用接口

需要哪個接口,就import哪個接口,返回的是Promise對象,非常方便。

<template> <div class="hello"> <button @click="_getStudentList">獲取學(xué)生列表</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>

總結(jié)

整個demo的代碼:demo

前端使用的整個流程:

1. 將后端提供的所有的proto文件拷進(jìn) src/proto 文件夾
2. 運行 npm run proto 生成proto.js
3. 根據(jù)接口枚舉在 src/api 下寫接口
4. .vue 文件中使用接口。
(其中1和2可以合并在一起寫一個自動化的腳本,每次更新只需運行一下這個腳本即可)。

寫的比較

主站蜘蛛池模板: 札达县| 湘阴县| 家居| 定边县| 汪清县| 彰化市| 万全县| 攀枝花市| 张家口市| 黄骅市| 手游| 大宁县| 宜兴市| 花莲县| 南郑县| 易门县| 宣汉县| 钟祥市| 香河县| 邻水| 民乐县| 望城县| 军事| 淮北市| 永城市| 屯门区| 石台县| 阳谷县| 四平市| 广元市| 田阳县| 博白县| 荔波县| 丰都县| 肥东县| 秭归县| 华阴市| 台东市| 米林县| 铜川市| 湖北省|