背景:目前,在开发基于微信的Web App应用,也就是借助微信所有资源,如公众号,账号系统和扫描JS-JDK等。后端是用node做中间件,依赖API服务(坑爹的是,API服务是用base64保存图片...)。现需实现一功能:用户选择图片,然后调用微信JS-JDK API,再上传至微信服务器(不能直接从微信里发送选择的图片),最后重微信服务器下载下来保存至我们自己服务器里。(下载下来后,需要转成data URIs)
关键代码:
javascript
controller.action('image', function * (next) { ...... token = yield base.getAccessToken(); // 获取access_token url = resource.genFetchImage(token, mediaId); // 组合请求图片的链接 response = yield request.get(url); // 通过co-request向微信服务器发出请求 // 处理响应,编码成base64 type = response.headers["content-type"]; prefix = "data:" + type + ";base64,"; base64 = new Buffer(response.body, 'binary').toString('base64'); this.body = prefix + base64; yield next; });
最后的响应结果也是有模有样的,看起来也很像data URIs,坑爹的这货是假的,假的。。。Google,百度,啥啥的都找了一遍,还是没结果(我寻找问题答案时,时常定位不准。。。),有以下答案的:
javascript
// 别说了,网上的十有八九就是我上面那种方式!
javascript
... base64 = new Buffer(response.body).toString('base64');
javascript
... base64 = response.body.toString('base64');
javascript
... base64 = new Buffer(response.body, 'utf8').toString('base64');
一一试了个遍,结果千奇百怪,千万个草泥马奔腾啊!!!都不行,后来还是解决了,才有这文章。其实原理很简单,只是对编码的理解不够而已。(小白我前端一枚,目前在较为深入学习node,想走全栈,反正路还很长呢!!)
javascript
... // 通过co-request向微信服务器发出请求 response = yield request.get({ url: url, encoding: null // 指定编码 }); response = response.body.toString('base64');
重点在request.get的参数上,{ encoding: null },我会慢慢讲解下去(大神勿喷!!)
--------------------------分割线--------------------------
在这里会涉及几个关键知识
Buffer是一个像Array的对象,但它主要用于操作字节,也就是二进制数据,它的元素为16进制的两位数,即0到255的数值。(欲想较为深入了解,看《深入浅出nodejs》)
关键API,具体参考 new Buffer 和 buf.toString
javascript
new Buffer(str[, encoding])
和
javascript
buf.toString([encoding][, start][, end])
字符编码,简单讲就是将我们显示器看到的字符编码成计算机识别的位(bit),比如:
这里有几个关键点:
���/u001d�)u�m/u001f�/u001a���E
这样常见的乱码是由于解码出错造成的。 �
, 重点 是 �
这货竟然有相应的utf8编码,编码为0xFFFD。这里有个关键点,很多 byte串 是无法正确解码的,但他们都会用 �
表示,而 �
字符又只有一种编码,所以对二进制数据如:图片,视频等,通过utf8编码并保存到变量后,是无法通过utf8原样解码成原来二进制的。 request 是非常非常强大的模拟浏览器发送HTTP请求的模块,非常非常强大!!而 co-request ,是通过TJ大神写的co模块简单对request包装了下,实现 yield + promise 优雅实现异步控制流,摆脱倒金字塔的利器!!
--------------------------分割线--------------------------
好,基础知识就差不多了,回到我之前遇到的问题上,并对其讲解下:
javascript
response = yield request.get(url); // 通过co-request向微信服务器发出请求 // 处理响应,编码成base64 type = response.headers["content-type"]; prefix = "data:" + type + ";base64,"; base64 = new Buffer(response.body, 'binary').toString('base64');
为什么这段代码有问题??对于 request 方法它有一个关键的参数 encoding
,默认值是 utf8
,所以 response.body
的值已经乱码的字符,再所以 new Buffer(response.body, 'binary')
将其转换成二进制时,已经不是原来的二进制了,这解释了为什么“最后的响应结果也是有模有样的,看起来也很像data URIs,坑爹的这货是假的,假的。。。”。而通过将 encoding
设为 null
,也就不对原始数据编码,保持原始的二进制图片数据。
网上的大多数的方案一,其实是没有错的!!!错在我遇到的错误和解决方案不拉边,它适用于通过 fs.createReadStream(path[, options]) 读取本地图片,并转换成base64编码。
而方案四我觉得特别有趣
javascript
... base64 = new Buffer(response.body, 'utf8').toString('base64');
我想, response.body
竟然是 utf8
编码的,那我可以通过 new Buffer(response.body, 'utf8')
,将其反编码成二进制数据(也就是binary),然后再转换成 base64 ,结果试了不行!!!最后痛苦的想了一遍,才发现自己脑短路了,也许这问题对很对大神来说很明显错误,可我还是觉得有趣,不懂的可以仔细想想。
到了最后了,我想答案很明显了,原理很简单,无非原始的二进制数据才是转换base64的正确数据。。。其它的错误都是“白忙活”惹的祸!!
小白之手,大神勿喷,欢迎意见,及时添正!