背景以及protobuf.js不在多交代。直接上使用方法和注意事项。
1、安装
npm i -S protobufjs
2、根据后台提供的.proto文件生成js
后台提供的.proto文件大概是如下这样的:
package PBServer;
message hello {
optional bytes name = 1;
.....
}
.......
主体结构就是上边这样。
在src下新建一个文件夹proto,把这个.proto文件放进去。
在项目根目录下执行下边脚本:
"scripts": {
"dev": "vite --https --mode dev",
"build": "vite build --mode prod",
/***** 在package.json文件中放置 proto 脚本 ********/
"proto": "pbjs -t json-module -w commonjs -o src/proto/proto.js src/proto/*.proto",
"preview":
直接执行 npm run proto。即可在proto文件夹下自动生成一个proto.js的文件。如果以后后台给你的proto文件变化了,直接运行 npm run proto即可重新生成新的proto.js。proto.js这个文件在后边帮助我们将proto翻译为json。
3、接收数据 解码数据
接受后台的数据,通常情况下是二进制数据,也就是blob。如下:
那么我现在要做的就是将二进制数据转为json数据。
实际上protobufjs内置了一些方法。解析方法如下:
// 第一步 通过lookup函数找到要解析的包名和字段名
// 如上,我们上边的.proto文件中包名是 PBserver,字段名是 hello。
import protobuf from 'protobufjs'
import protoRoot from '@/proto/proto.js'
// 找到要翻译的包和字段
const pb = protobuf.lookup('PBserver.hello');
// 然后使用内置方法将数据转为ArraryBuffer
const buf = protoRoot.util.newBuffer(data);
// 解码 decode内置方法
return pb.decode(buf)
以上是使用内置方法处理二进制数据。
但是
我在vue3项目中使用的时候,出现了问题。所有解析出来的数据全部为空!!!
经过一步一步分析之后,发现是二进制转buffer这一步出现问题,转完buffer之后,数据全部为空。所以怀疑是转buffer这里的方法出了什么问题。
既然这样,就自己去写转buffer的方法,亲测有效,代码如下:
......
const fileReader = new FileReader();
const proto = protoRoot.lookupType('PBserver.hello');
fileReader.onload = function () {
const data = this.result;
const u8 = new Uint8Array(data as ArrayBuffer);
const msg = proto.decode(u8);
// 最后这个 msg 就是我们成功解析的数据
};
......
4、最后注意
如果你使用的vuecli创建的项目,那么这条注意就不用看了,因为大概率不会出现问题。
如果使用的是vite构建的项目,那么在proto.js中会出现报错:
require is undefined
因为我们生成protojs的时候使用的commonjs,所以包引入的时候全部使用的 require 和 module.exports。但是vite打包构建是基于es module(import export)的。所以报错了。
这个只要将出现 require 的地方改为 import from,module.exports 的地方改为 export (default) 即可。
________________更新_____________________________
5、异步解析
请注意,fileReader.onload 这个函数是异步解析的,所以操作的时候要注意,否则数据会接受不全。
比如现在,这些数据全部是异步后台请求的PB数据,需要分别解析每个数据,然后再push进数组,那么这时候请使用 promise。
代码如下:
// 注意 这是我写的一个闭包 目的是不让 dataTemp 过早释放,导致数据不能接受完全
const receiveMsg = (function () {
var dataTemp: any = [];
return function (e: any) {
if (typeof e.data != 'string') {
dataTemp.push(e.data);
} else {
// 9003 代表后台数据传输完毕
if (e.data.indexOf('9003') > -1) {
// 在这里使用promise -》transformData 函数如下
const final = dataTemp.map(async (item: any) => {
return await transformData(item);
});
// 要注意 这个 final 最后是一个 promise的数组 需要全部执行 得到结果
// 所以如下使用all方法
Promise.all(final).then((res) => {
worker1.postMessage(res);
// 清除闭包占有空间
dataTemp = null;
});
}
}
};
})();
// transformData 函数使用promise处理接受数据
import protoRoot from '@/proto/proto.js';
export const transformData = function (item: any) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
const proto = protoRoot.lookupType('DMSDataFile.DataFileItem');
fileReader.readAsArrayBuffer(item);
fileReader.onload = function () {
const data: any = this.result;
const u8 = new Uint8Array(data as ArrayBuffer);
resolve(proto.decode(u8));
};
});
};
6、其他转化的方法 比如 unit8Array 转化为 string
这里直接提供一个比较完整的版本,转换的时候不会出现中文乱码。
// unit8array 转 string
function Uint8ArrayToString(array) {
var out, i, len, c;
var char2, char3;
out = '';
len = array.length;
i = 0;
while (i < len) {
c = array[i++];
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12:
case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(
((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0)
);
break;
}
}
return out;
}