10bet:如何在前端中使用protobuf

来源:http://www.chinese-glasses.com 作者:Web前端 人气:177 发布时间:2020-03-31
摘要:let _proto = null// 将所有的.proto存放在_proto中function loadProtoDir (dirPath) { const files = fs.readdirSync(dirPath) const protoFiles = files .filter(fileName = fileName.endsWith('.proto')) .map(fileName = path.join(dirPath, file
let _proto = null// 将所有的.proto存放在_proto中function loadProtoDir (dirPath) { const files = fs.readdirSync(dirPath) const protoFiles = files .filter(fileName = fileName.endsWith('.proto')) .map(fileName = path.join(dirPath, fileName)) _proto = ProtoBuf.loadSync(protoFiles).nested return _proto}// 根据typeName获取PBMessagefunction lookup (typeName) { if (!_.isString(typeName)) { throw new TypeError('typeName must be a string') } if (!_proto) { throw new TypeError('Please load proto before lookup') } return _.get(_proto, typeName)}function create (protoName, obj) { // 根据protoName找到对应的message const model = lookup(protoName) if (!model) { throw new TypeError(`${protoName} not found, please check it again`) } const req = model.create(obj) return model.encode(req).finish()}module.exports = { lookup, // 这个方法将在request中会用到 create, loadProtoDir}

(其中1和2可以合并在一起写一个自动化的脚本,每次更新只需运行一下这个脚本即可)。

这里要建议先看一下MessageType.proto,其中定义了与后端约定的接口枚举、请求体、响应体。

-w参数可以指定打包js的包装器,这里用的是commonjs,详情请各位自己去看文档。运行命令后在src/proto目录下生成的proto.js。在chrome中console.log(proto.js)一下:

我们使用时只需要以PBStudentListReq和dataObj作为参数即可,无需关心PBStudentListReq是在哪个.proto文件中。这里有个难点:如何根据类型来找到所在的.proto呢?

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

这里只告诉了这个接口的请求体是PBStudentListReq,返回值是PBStudentListRsp,而它们所在的.proto文件是不知道的。

虽然语法简单,但其实前端不用怎么关心如何写proto文件,一般都是由后端来定义和维护。在这里大家可以直接用一下我定义好的一份demo。

如果我们的项目中只有一个.proto文件,我们完全可以像官方文档这样用。但是在实际项目中,往往是有很多个.proto文件的,如果每个PBMessage都要先知道在哪个.proto文件中,使用起来会比较麻烦,所以需要封装一下。服务端同学给我们的接口枚举中一般是这样的:

准备工作1.拿到一份定义好的proto文件。

protobuf.load("awesome.proto", function(err, root) { if (err) throw err; var AwesomeMessage = root.lookupType("awesomepackage.AwesomeMessage"); var payload = { awesomeField: "AwesomeString" }; var message = AwesomeMessage.create(payload); var buffer = AwesomeMessage.encode(message).finish();});

前端使用的整个流程:

为了使用方便,我们希望封装一个方法,形如:

同时添加安装axios和protobufjs。

参考下官方文档将object转化为buffer的方法:

protobuf.js提供了几种方式来处理proto。

const rp = require('request-promise') const proto = require('./proto.js') // 上面我们封装好的proto.js/** * * @param {* 接口名称} msgType * @param {* proto.create()后的buffer} requestBody * @param {* 返回类型} responseType */function request (msgType, requestBody, responseType) { // 得到api的枚举值 const _msgType = proto.lookup('framework.PBMessageType')[msgType] // PBMessageRequest是公共请求体,携带一些额外的token等信息,后端通过type获得接口名称,messageData获得请求数据 const PBMessageRequest = proto.lookup('framework.PBMessageRequest') const req = PBMessageRequest.encode({ timeStamp: new Date().getTime(), type: _msgType, version: '1.0', messageData: requestBody, token: 'xxxxxxx' }).finish() // 发起请求,在vue中我们可以使用axios发起ajax,但node端需要换一个,比如"request" // 我这里推荐使用一个不错的库:"request-promise",它支持promise const options = { method: 'POST', uri: '_server.com/api', body: req, encoding: null, headers: { 'Content-Type': 'application/octet-stream' } } return rp.post(options).then((res) = { // 解析二进制返回值 const decodeResponse = proto.lookup('framework.PBMessageResponse').decode(res) const { resultInfo, resultCode } = decodeResponse if (resultCode === 0) { // 进一步解析解析PBMessageResponse中的messageData const model = proto.lookup(responseType) let msgData = model.decode(decodeResponse.messageData) return msgData } else { throw new Error(`Fetch ${msgType} failed.`) } })}module.exports = request
# vue create vue-protobuf# npm install axios protobufjs --save-dev

原文:

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

方法是:把所有的.proto放进内存中,然后根据名称获取对应的类型。

所以说proto文件可以直接作为前后端沟通的文档。步骤1.新建一个vue项目

demo源码

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

而在node端,也可以打包成js文件来处理。但node端是服务端环境了,完全可以允许.proto的存在,所以其实我们可以有优雅的使用方式:直接解析。

可以发现,这个模块在原型链上定义了load,lookup等非常有用的api,这正是后面我们将会用到的。为以后方便使用,我们将命令添加到package.json的script中:

const request = require('./request')const proto = require('./proto')exports.getStudentList = function getStudentList (params) { const req = proto.create('school.PBStudentListReq', params) return request('school.getStudentList', req, 'school.PBStudentListRsp')}
template div  button @click="_getStudentList"获取学生列表/button /div/templatescriptimport { 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 () { }}/scriptstyle lang="scss"/style

使用

最后写好的具体代码请看:request.js。其中用到了lookup(),encode(),finish(),decode()等几个proto.js提供的方法。

这里要求,在使用create和lookup前,需要先loadProtoDir,将所有的proto都放进内存。

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

request.js和proto.js提供底层的服务,为了使用方便,我们还要封装一个api.js,定义项目中所有的api。

拿到后端提供的这份*.proto文件后,是不是已经可以基本了解到:有一个getStudentList的接口,请求参数是PBStudentListReq,返回的参数是PBStudentListRsp。

_proto类似一颗树,我们可以遍历这棵树找到具体的类型,也可以通过其他方法直接获取,比如lodash.get()方法,它支持obj['xx.xx.xx']这样的形式来取值。

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

预期效果

原文:

准备工作:

前端中需要使用protobuf.js这个库来处理proto文件。

封装两个基础模块:

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

应该比较容易理解:先loadawesome.proto,然后将数据payload转变成我们想要的buffer。create和encode都是protobufjs提供的方法。

  1. 在.vue中使用接口

request.js: 用于根据接口名称、请求体、返回值类型,发起请求。proto.js用于解析proto,将数据转换为二进制。

后面有时间我会再写一篇在node端动态解析proto的记录。

之前提到,在vue中,为了避免直接使用.proto文件,需要将所有的.proto打包成.js来使用。

其实不用去学习proto的语法都能一目了然。这里有两种命名空间framework和school,PBStudent引用了PBUser,可以认为PBStudent继承了PBUser。

// lib/api.js 封装APIconst request = require('./request')const proto = require('./proto')/** * * @param {* 请求数据} params * getStudentList 是接口名称 * school.PBStudentListRsp 是定义好的返回model * school.PBStudentListReq 是定义好的请求体model */exports.getStudentList = function getStudentList (params) { const req = proto.create('school.PBStudentListReq', params) return request('school.getStudentList', req, 'school.PBStudentListRsp')}// 项目中使用lib/api.jsconst api = require('../lib/api')const req = { limit: 20, offset: 0}api.getStudentList(req).then((res) = { console.log(res)}).catch(() = { // ...})
// 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; // 小学生 MIDDLE = 1; // 中学生 SENIOR = 2; // 高中生 COLLEGE = 3; // 大学生}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; // 请求数据 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}

在项目中可以这样使用:

总结

封装request.js

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

封装proto.js

在.vue文件直接调用api前,我们一般不直接使用request.js来直接发起请求,而是将所有的接口再封装一层,因为直接使用request.js时要指定请求体,响应体等固定的值,多次使用会造成代码冗余。

同样是要使用protobuf.js这个库来解析。

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

时间: 2019-10-21阅读: 97标签: 库解析思路

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(...)}
const reqData = {a: '1'}const message = PBMessage.create(reqData)const reqBuffer = PBMessage.encode(message).finish()

我们习惯上在项目中将所有后端的接口放在src/api的目录下,如针对student的接口就放在src/api/student.js文件中,方便管理。将getStudentList的接口写在src/api/student.js中

const _ = require('lodash')const PBMessage = _.get(_proto, 'school.PBStudentListReq')

整个demo的代码:demo。

在项目中使用接口时,只需要require('lib/api'),不直接引用proto.js和request.js。

直接解析,如protobuf.load("awesome.proto", function(err, root) {...})转化为JSON或js后使用,如protobuf.load("awesome.json", function(err, root) {...})其他

本文由10bet发布于Web前端,转载请注明出处:10bet:如何在前端中使用protobuf

关键词:

最火资讯