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这种操做了。