如何在前端中使用protobuf(node篇)


解析思路

同樣是要使用protobuf.js這個庫來解析。

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

而在node端,也可以打包成js文件來處理。但node端是服務端環境了,完全可以允許.proto的存在,所以其實我們可以有優雅的使用方式:直接解析。

 

預期效果

封裝兩個基礎模塊:

  • request.js: 用於根據接口名稱、請求體、返回值類型,發起請求。
  • proto.js用於解析proto,將數據轉換為二進制。

在項目中可以這樣使用:

// lib/api.js 封裝API const 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.js const api = require('../lib/api') const req = { limit: 20, offset: 0 } api.getStudentList(req).then((res) => { console.log(res) }).catch(() => { // ... })

 

准備工作:

准備如何在前端中使用protobuf(vue篇)中定義好的一份.proto,注意這份proto中定義了兩個命名空間:framework和school。proto文件源碼

 

封裝proto.js

參考下官方文檔將object轉化為buffer的方法:

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(); });

應該比較容易理解:先load awesome.proto,然后將數據payload轉變成我們想要的buffer。create和encode都是protobufjs提供的方法。

如果我們的項目中只有一個.proto文件,我們完全可以像官方文檔這樣用。
但是在實際項目中,往往是有很多個.proto文件的,如果每個PBMessage都要先知道在哪個.proto文件中,使用起來會比較麻煩,所以需要封裝一下。
服務端同學給我們的接口枚舉中一般是這樣的:

getStudentList = 0; // 獲取所有學生的列表, PBStudentListReq => PBStudentListRsp

這里只告訴了這個接口的請求體是PBStudentListReq,返回值是PBStudentListRsp,而它們所在的.proto文件是不知道的。

為了使用方便,我們希望封裝一個方法,形如:

const reqBuffer = proto.create('school.PBStudentListReq', dataObj)

我們使用時只需要以PBStudentListReq和dataObj作為參數即可,無需關心PBStudentListReq是在哪個.proto文件中。
這里有個難點:如何根據類型來找到所在的.proto呢?

方法是:把所有的.proto放進內存中,然后根據名稱獲取對應的類型。

寫一個loadProtoDir方法,把所有的proto保存在_proto變量中。

// proto.js const fs = require('fs') const path = require('path') const ProtoBuf = require('protobufjs') 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 }

_proto類似一顆樹,我們可以遍歷這棵樹找到具體的類型,也可以通過其他方法直接獲取,比如lodash.get()方法,它支持obj['xx.xx.xx']這樣的形式來取值。

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

這樣我們就拿到了順利地根據類型在所有的proto獲取到了PBMessage,PBMessage中會有protobuf.js提供的create、encode等方法,我們可以直接利用並將object轉成buffer。

const reqData = {a: '1'} const message = PBMessage.create(reqData) const reqBuffer = PBMessage.encode(message).finish()

整理一下,為了后面使用方便,封裝成三個函數:

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獲取PBMessage function 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 }

這里要求,在使用create和lookup前,需要先loadProtoDir,將所有的proto都放進內存。

 

封裝request.js

這里要建議先看一下MessageType.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: 'http://your_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

資源搜索網站大全 http://www.szhdn.com 廣州VI設計公司https://www.houdianzi.com

使用

request.js和proto.js提供底層的服務,為了使用方便,我們還要封裝一個api.js,定義項目中所有的api。

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

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

// test.js const api = require('../lib/api') const req = { limit: 20, offset: 0 } api.getStudentList(req).then((res) => { console.log(res) }).catch(() => { // ... })


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM