前言
之前一直是搞Android开发,最近公司要上小程序,前后端数据交互是用的protobuf,js啥的都已经还给老师了,反正是边学边搞吧!这里选择了uniapp作为微信小程序的开发,看了uniapp官网的介绍,感觉挺吊的。
第一步:protobuf的编解码
先准备好proto文件
syntax = "proto3";
package common;
message ErrorInfo {
fixed32 errorCode = 1; //错误码
bytes errorMessage = 2; //错误描述信息
}
message AwesomeMessage{
fixed32 version = 1;
fixed32 app = 2;
fixed32 server = 3;
fixed32 servant = 4;
bytes data = 9;
}
protobuf的编解码就要用到protobufjs
进入你的项目目录安装protobufjs,这里顺便把axios也安装了(后来发现axios在微信小程序里用不了)前端http请求基本都是用的axios
npm install axios protobufjs --save
生成proto的js文件,在这里我们用static-module 来生成js文件,和json-module有啥区别我目前也不知道。目前发现用json-module,使用lookup和lookuptype在微信小程序中报错,h5可以正常编解码。
npx pbjs -t static-module -w commonjs -o proto/bundle.js proto/*.proto
接下来是使用这个bundle.js来进行编解码
import {common} from '../../proto/bundle.js';
let errinfo = common.ErrorInfo.create({
errorCode: 0,
errorMessage:"success"
})
//把ErrorInfo对象 编码成Uint8Array (browser) or Buffer (node)
let errBuffer = common.ErrorInfo.encode(errinfo).finish()
//把Uint8Array (browser) 或者 Buffer (node) 解码成ErrorInfo对象
let message = common.ErrorInfo.decode(errBuffer)
//转化为一个对象
let obj = common.ErrorInfo.toObject(message, {
enums: String, // enums as string names
longs: String, // longs as strings (requires long.js)
bytes: String,
})
console.log(obj);
输出的结果
{errorCode: 0, errorMessage: "success="}
结果打印出来发现多了一个= ,然后看官方文档:
var object = AwesomeMessage.toObject(message, {
enums: String, // enums as string names
longs: String, // longs as strings (requires long.js)
bytes: String, // bytes as base64 encoded strings
defaults: true, // includes default values
arrays: true, // populates empty arrays (repeated fields) even if defaults=false
objects: true, // populates empty objects (map fields) even if defaults=false
oneofs: true // includes virtual oneof fields set to the present field's name
});
//bytes as base64 encoded strings
字节作为base64编码的字符串, 那去掉bytes: String, 结果打印成了字节数组,
{errorCode: 0, errorMessage: Uint8Array(5)}
接下来试试用中文,结果直接报错了"Error: invalid encoding"
最后修改的代码是这样
import {common} from '../../proto/bundle.js';
let errinfo = common.ErrorInfo.create({
errorCode: 0,
errorMessage:Buffer.from('成功')
})
//把ErrorInfo对象 编码成Uint8Array (browser) or Buffer (node)
let errBuffer = common.ErrorInfo.encode(errinfo).finish()
//把Uint8Array (browser) 或者 Buffer (node) 解码成ErrorInfo对象
let message = common.ErrorInfo.decode(errBuffer)
//转化为一个对象
let obj = common.ErrorInfo.toObject(message, {
enums: String, // enums as string names
longs: String, // longs as strings (requires long.js)
defaults: true,//使用默认值,要不然需要判断undefined
//bytes: String,
})
console.log(obj);
let buf = Buffer.from(obj.errorMessage)
console.log(`errorMessage = ${buf.toString()}`);
打印出来结果对了,然后在小程序模拟器上运行也是可以的
errorMessage = 成功
第二步:通过http把protobuf数据发给服务器,响应之后再解码protobuf得到结果
接下来是登录的proto文件
syntax = "proto3";
package user;
import "Common.proto";
message login_req
{
bytes phone = 1;
uint32 type = 2;
bytes verify_info = 3;
}
message login_rsp {
ErrorInfo errInfo = 1; // 错误码信息
uint32 id = 2;
bytes token = 3;
}
接下来是proto的编码,修改了或者新增了proto文件之后都要通过pbjs重新生成一下js文件
import {common, user} from '../../proto/bundle.js';
const axios = require('axios');
let loginMessage = user.login_req.create({
phone: Buffer.from('1234567890'),
type: 1,
verifyInfo: Buffer.from('123456a'),//这里要注意如果按照proto文件的来verify_info就错了
})
//proto对象转buffer
let buffer = user.login_req.encode(loginMessage).finish()
console.log(buffer);
let requestMessage = common.AwesomeMessage.create({
version: 1,
app: 1,
server: 2,
servant: 1005,
data: buffer,
})
let requestBuffer = common.AwesomeMessage.encode(requestMessage).finish()
console.log(requestBuffer);
注意 开始是这样写的 js对象的verify_info属性对应于proto文件的verify_info,结果发现和java打印出来的字节数组对比,发现少了几个,后来打印loginMessage发现verify_info对应的值没了,然后去bundle.js发现了这个如下图
let loginMessage = user.login_req.create({
phone: Buffer.from('1234567890'),
type: 1,
verify_info: Buffer.from('123456a'),//错误,应该是verifyInfo
})
proto采用驼峰命名原则更好一些
接下来就是前端使用axios的http请求了
function transformRequest(data) {
return common.MsgWebsocket.encode(requestMessage).finish()
}
axios.create({
timeout: 15000,
method: 'post',
headers: {
"X-Requested-With": "XMLHttpRequest",
"Content-Type": "application/octet-stream",
},
responseType: 'arraybuffer',
}).post('http://********', requestMessage, {
transformRequest: transformRequest
})
.then((response) => {
console.log(response);
if (response.status === 200) {
try {
let enc = new TextDecoder('utf-8')
let res = JSON.parse(enc.decode(new Uint8Array(response.data))) //转化成json对象
console.log(res);
} catch (e) {
//let resBuf = protobuf.util.newBuffer(response.data)
let resBuf = Buffer.from(response.data)
let resMessage = common.AwesomeMessage.decode(resBuf)
let loginRspBuf = resMessage.data
let loginRspMessage = user.login_rsp.decode(loginRspBuf)
let obj = user.login_rsp.toObject(loginRspMessage, {
longs: String,
enums: String,
})
console.log(obj);
console.log(`errorMessage = ${Buffer.from(loginRspMessage.errInfo.errorMessage).toString()}`);
}
}
}, (err) => {
console.log(err);
});
大家可以看到为什么要转换为json对象,不是说的protobuf吗?这里好像是后端的一个bug
请求成功时,后端返回protobuf字节数组
请求失败时,后端返回json对象
此时请求成功和失败返回的http状态码都是200
ok,打印出服务器返回的protobuf结果了
好,接下来就是在微信小程序中运行了,结果报错
出错是在这一行,发现是axios这里报错了
星星你个星星,微信小程序不支持axios!!!!!
第三步:在微信小程序中通过http把protobuf数据发给服务器,响应之后再解析protobuf得到结果
在这里uniapp已经封装了http的请求的api 和小程序的http请求的api基本上用法是一样的
重新写个方法wxhttpTest
uni.request({
url: 'http://xxxxxxxxxxxxx',
header: {
"X-Requested-With": "XMLHttpRequest",
"Content-Type": "application/octet-stream",
},
method: 'POST',
timeout: 15000,
dataType: 'protobuf',//随便写,只要不是json就行
responseType: 'arraybuffer',
data: requestBuffer
}).then((res) => {
console.log(res);
//返回一个response的数组回来,第一个是null,第二个才是服务器返回的response
//不知道是uniapp的问题还是微信小程序的问题
for (let response of res) {
if (response !== null && response !== undefined && response.statusCode === 200) {
try {
let jsonStr = Buffer.from(response.data).toString()
let resJson = JSON.parse(jsonStr) //转化成json对象
console.log(resJson);
} catch (e) {
//let resBuf = protobuf.util.newBuffer(response.data)
let resBuf = Buffer.from(response.data)
let resMessage = common.AwesomeMessage.decode(resBuf)
let loginRspBuf = resMessage.data
let loginRspMessage = user.login_rsp.decode(loginRspBuf)
let obj = user.login_rsp.toObject(loginRspMessage, {
longs: String,
enums: String,
defaults: true,
})
console.log(obj);
console.log(`errorMessage = ${Buffer.from(loginRspMessage.errInfo.errorMessage).toString()}`);
}
}
}
}, (err) => {
console.log(err);
});
结果返回的是json数据,数据输入错误
也就是说后端收到的protobuf数据解析失败,然后返回json的错误给小程序了,到这里直接懵逼了,怎么办
继续搜索大法
在微信的开发社区搜索到这一篇
然后我加上了这个
requestBuffer = new Uint8Array([...requestBuffer]).buffer
竟然好了,拿到了后端返回的protobuf数据,并且解析成功了
最后 Android和ios真机调试也通过了。我是初学者,为什么加了new Uint8Array([...requestBuffer]).buffer这行代码就可以了,有大佬可以解释一下的吗?
最后感谢这些博文:
axios请求设置responseType为'blob'或'arraybuffer'下载文件时,正确处理返回值为文件流或json对象的情况