vue中使用protobuf的过程记录

由于目前公司采用了ProtoBuf做前后端数据交互,进公司以来一直用的是公司大神写好的基础库,完全不了解底层是如何解析的,一旦报错只能求人,作为一只还算有钻研精神的猿,应该去了解一下底层的实现,在这里记录一下学习过程。

Protobuf简单介绍

Google Protocol Buffer(简称 Protobuf)是一种轻便高效的结构化数据存储格式,平台无关、语言无关、可扩展,可用于通讯协议和数据存储等领域。

有几个优点:

1.平台无关,语言无关,可扩展;
2.提供了友好的动态库,使用简单;
3.解析速度快,比对应的XML快约20-100倍;
4.序列化数据非常简洁、紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。

个人感受: 前后端数据传输用json还是protobuf其实对开发来说没啥区别,protobuf最后还是要解析成json才能用。个人觉得比较好的几点是:

1.前后端都可以直接在项目中使用protobuf,不用再额外去定义model;
2.protobuf可以直接作为前后端数据和接口的文档,大大减少了沟通成本;

没有使用protobuf之前,后端语言定义的接口和字段,前端是不能直接使用的,前后端沟通往往需要维护一份接口文档,如果后端字段有改动,需要去修改文档并通知前端,有时候文档更新不及时或容易遗漏,沟通成本比较大。 使用protobuf后,protobuf文件由后端统一定义, protobuf直接可以作为文档 ,前端只需将protobuf文件拷贝进前端项目即可。如果后端字段有改动,只需通知前端更新protobuf文件即可,因为后端是直接使用了protobuf文件,因此protobuf文件一般是不会出现遗漏或错误的。长此以往,团队合作效率提升是明显的。

废话了一大堆,下面进入正题。 我这里讲的主要是在vue中的使用,是目前本人所在的公司项目实践,大家可以当做参考。

思路

前端中需要使用 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 ,然后再利用库提供的方法来解析数据,最后得到数据对象。

PS: 实践发现,转化为js文件会更好用一些,转化后的js文件直接在原型链上定义了一些方法,非常方便。因此后面将会是使用这种方法来解析proto。

预期目标

在项目中封装一个 request.js 模块,希望能像下面这样使用,调用api时只需指定请求和响应的model,然后传递请求参数,不需关心底层是如何解析proto的,api返回一个Promise对象:

// /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)
  })
 }
 }
}

准备工作

1.拿到一份定义好的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文件可以直接作为前后端沟通的文档。

步骤

1.新建一个vue项目

同时添加安装 axios 和 protobufjs 。

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

2.在 src 目录下新建一个 proto 目录,用来存放 *.proto 文件,并将写好的proto文件拷贝进去。

此时的项目目录和 package.json :

3.将 *.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文件,查看请点击这里。 之前说了:实践证明打包成js模块才是最好用的。我这里直接给出最终的命令

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。

4. 封装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。 其中用到了 lookup() , encode() , finish() , decode() 等几个proto.js提供的方法。

5. 调用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(...)
}

6. 在.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>

总结

整个demo的代码:demo

前端使用的整个流程:

1. 将后端提供的所有的proto文件拷进 src/proto 文件夹
2. 运行 npm run proto 生成proto.js
3. 根据接口枚举在 src/api 下写接口
4. .vue 文件中使用接口。
(其中1和2可以合并在一起写一个自动化的脚本,每次更新只需运行一下这个脚本即可)。

写的比较啰嗦,文笔也不好,大家见谅。

这个流程就是我感觉比较好的一个proto在前端的实践,可能并不是最好,如果在你们公司有其他更好的实践,欢迎大家一起交流分享。

后续

在vue中使用是需要打包成一个js模块来使用比较好(这是因为vue在生产环境中打包成只有html,css,js等文件)。但在某些场景,比如在Node环境中,一个Express的项目,生产环境中是允许出现 .proto 文件的,这时候可以采取 protobuf.js 提供的其他方法来动态解析proto,不再需要npm run proto这种操作了。

以上所述是小编给大家介绍的vue中使用protobuf的过程记录,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • vue中使用protobuf的过程记录

    由于目前公司采用了ProtoBuf做前后端数据交互,进公司以来一直用的是公司大神写好的基础库,完全不了解底层是如何解析的,一旦报错只能求人,作为一只还算有钻研精神的猿,应该去了解一下底层的实现,在这里记录一下学习过程. Protobuf简单介绍 Google Protocol Buffer(简称 Protobuf)是一种轻便高效的结构化数据存储格式,平台无关.语言无关.可扩展,可用于通讯协议和数据存储等领域. 有几个优点: 1.平台无关,语言无关,可扩展: 2.提供了友好的动态库,使用简单; 3

  • 如何在Vue中使用protobuf

    protobuf是由google推出的和语言无关和平台无关,可扩展的序列化数据结构协议,类似于XML,但是比XML更小.更快.更简单.protobuf几乎支持当前的大部分语言,如JavaScript  安装protobufjs cnpm i -S protobufjs 注意:当前protobufjs的版本为:6.11.2 在项目src目录下新建proto目录,把后端给的test.proto文件放进去 syntax = "proto3";//第一行指定了正在使用proto3语法:如果你没

  • 详解vue中使用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 c

  • vue粘贴复制功能的实现过程记录

    目录 引言 1. 项目需求图展示: 1. 安装第三方插件方法(不推荐) 2. 浏览器自带Document.execCommand()复制方法(不推荐) 3. Clipboard.writeText方法(强烈推荐) 总结 引言 项目中如果实现粘贴复制功能,目前市面上共有三种方法,均有利有弊,大家可以根据自己项目实际情况依次选择.本节将都会对这三种方法做详细阐述,重点推荐第三种方法. 1. 项目需求图展示: 1. 安装第三方插件方法(不推荐) 这种方法兼容性很好,如果项目只使用了一次,不建议使用.

  • 脚手架(vue-cli)创建Vue项目的超详细过程记录

    目录 cli3.x/4.x创建Vue项目 1.创建项目 2.项目手动配置 3.选择vue版本 4.路由模式 5.配置文件的存放位置 6.配置保存 项目创建成功 总结 cli3.x/4.x创建Vue项目 1.创建项目 vue create vue_project(项目名) 敲击回车,出现以下页面 此处本人一般选择手动配置,因为eslint真的是个很可怕的东西,它对代码格式的严格要求,真的会把人逼疯的,代码有一点点不规范的地方程序都会报错 2.项目手动配置 按空格键选择或取消选择 3.选择vue版本

  • 在vue中使用SockJS实现webSocket通信的过程

    最近接到一个业务需求,需要做一个聊天信息的实时展示的界面,这就需要和服务器端建立webSocket连接,从而实现数据的实时获取和视图的实时刷新.在此将我的实现记录下来,希望可以给有同样需求的人一些帮助.废话少说,下面我就来讲一下我的实现过程: socksjs •客户端和服务器端api尽可能简洁,尽量靠近websocket api •支持服务端扩展和负载均衡技术 •传输层应该全面支持跨域通信 •如果受到代理服务器的限制,传输层能优雅地从一种方式回退到另一种方式 •尽可能快地建立连接 •客户端只是纯

  • vue中使用微信公众号js-sdk踩坑记录

    最近又在vue中捣鼓了下微信公众号api的接入,不得不说这里边水是真的深啊,上次分享了微信授权登录和js-sdk签名的部分,其中很多朋友私信我表示了疑惑,今天我就再次尝试理顺一下这里边的坑吧: 微信JS-SDK是微信公众平台面向网页开发者提供的基于微信内的网页开发工具包. 通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照.选图.语音.位置等手机系统的能力,同时可以直接使用微信分享.扫一扫.卡券.支付等微信特有的能力,为微信用户提供更优质的网页体验. 分享页面到朋友圈 上文是从官方文档

  • vue 中使用 vxe-table 制作可编辑表格的使用过程

    项目上有一个表格需要实现在线编辑,开始用了 element 的el-table 实现,单元格内基础情况就是监听了单击单元格切换一个span标签与input标签,复杂点的单元格使用了大量的条件判断来实现对应的编辑操作,比如下拉选,popover弹框编辑.整个表格几十列,十几条数据就已经出现了明显的卡顿,在做了诸多操作(比如el-input使用原生input替换.减少判断.减少频繁的数据切换等)之后,速度虽然有所提升,但是还是肉眼可见的卡顿,基本不可用.然后便转战vxe-table,重写了一遍这个表

  • vue中npm包全局安装和局部安装过程

    全局安装是将npm包安装在你的node安装目录下的node_modules文件夹中.在windows和mac中,全局安装的默认路径是不同的.在mac中默认是安装到 /usr/locla/lib 中.在windows默认安装目录是 C:\Program Files\nodejs ,当然你也可以通过一下命令来查看全局安装路径. // 查看全局安装路径 npm root -g // 查看npm的基础设置 npm config ls // 查看安装目录路径 npm config get prefix 全

  • 如何在vue中使用jointjs过程解析

    在vue中引入joint.js的问题,之前在网上搜了很多,都没有给出一个确切的答案,捣鼓了两天终于弄明白了,做个记录. 首先,我参考了一篇来自stackoverflow的文章 看完这篇文章,大家应该至少大致怎么做了,下面我们来具体看一下: 首先在vue项目中运行npm install jointjs --save 然后在入口文件,我的是main.js,也有可能是app.js中加入下面两行,把joint.js和jquery作为全局变量 window.$ = require('jquery'); w

随机推荐