8

小程序生成二维码无法Node.js保存

 1 year ago
source link: https://www.daguanren.cc/post/wxacode_saved_problem.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
07月29, 2019

小程序生成二维码无法Node.js保存

有这样一个场景:

张三的程序P1是用HTML开发的,部分页面结合了微信的JS-SDK。

李四的程序P2是完全使用微信小程序开发的。

现在张三想和李四合作,将P1和P2整合成一个完整的程序P。有一种方案A,是把P2再开发个HTML版本后和P1整合。

但是问题来了。P2中的一些功能使用HTML浏览器无法完美实现,以iOS移动端为例,比如其中一个自定义相机的功能A在微信内浏览器不支持,而在Safari中支持。检测设备方向的功能,在微信内浏览器支持,而在Safari不支持。这就很搞了,有么有?

于是只能另辟蹊径,采用另外一种方案B,在P1的HTML程序中调用微信获取小程序码API实现不同用户生成不同的参数的小程序码图片,用户长按扫码后进入小程序使用响应的功能。

此方案理论上是可行的,不过做的过程中又遇到了问题,即调用API生成的小程序码使用Node.js保存出错,下面就来看下如何解决这个问题。

这里先列下我的运行环境:

  • macOS 10.14.5
  • Node v10.15.3
  • ThinkJS 2.2.8

一开始我的代码是这么写的:

const Base = require('./base.js');const rp = require('request-promise');const fs = require('fs');module.exports = class extends Base { async testAction() { let getUnlimitedUrl = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token='; let access_token = '填入你自己的微信小程序的access_token'; getUnlimitedUrl += access_token; const options = { method: 'POST', uri: getUnlimitedUrl, body: { // scene: '', scene: 'id=160', page: 'pages/index/index' json: true // Automatically stringifies the body to JSON let result = await rp(options); await fs.writeFileSync(think.ROOT_PATH + '/www/static/image/qrcode_test1.jpg', result); return this.success(result);javascript

你会发现,卧槽,这个代码写的太好了,完全没有错误。但是,当我点击保存下来的图片预览时,图片却一直在转圈圈。就是下面这个鬼样子:

这是咋回事呢?难道苍天嫉妒我?我查看了下微信返回的数据,发下是如下这个鸟样:

一大段乱码诶。而且数据的格式并不是你微信API宣称的那样严谨:

算了,我也不跟你计较。但是这一堆乱码代表的啥,确实不懂。于是开始Google和百度。网上类似的问题有很多,但方法一个一个尝试后,都是无效的,例如网上提到的如下方法:

  • 方案一:在头尾像模像样的增加base64编码所缺的字符

  • controller.action('image', function * (next) {
......token = yield base.getAccessToken(); // 获取access_tokenurl = resource.genFetchImage(token, mediaId); // 组合请求图片的链接response = yield request.get(url); // 通过co-request向微信服务器发出请求// 处理响应,编码成base64type = response.headers["content-type"];prefix = "data:" + type + ";base64,";base64 = new Buffer.from(response.body, 'binary').toString('base64');this.body = prefix + base64;yield next;qml 方案二:将返回的数据先转成buffer再转成base64的字符串
base64 = new Buffer.from(response.body).toString('base64'); 方案三:将返回的数据直接转成base64的字符串
base64 = response.body.toString('base64'); 方案四:将返回的数据先转成'utf8'的buffer,再转成base64的字符串
base64 = new Buffer(response.body, 'utf8').toString('base64');

这四个方案都是扯淡!!!!都是扯淡!!!!根本不是这个原因。

后来去研究图片保存到计算机上一般以什么形式保存?得到答案:字节(byte)。嗯,好熟悉的样子,好像想起了1byte = 8 bit。

如上图一个5像素*5像素(共25像素)的RGB图片,一共有25× 8× 3个字节,大概占用0.6kb。当然这个图片太小了,有点不正常。

举个正常的:以一张尺寸为900 × 600的图片为例,图片共有像素数:

900 × 600 = 540,000像素(Pixel)。

如果图片是RGB 色彩模式,占用的空间是:

900 × 600 × 3 = 1,620,000 字节(bytes).

大部分程序系统使用兆来衡量图片大小,下面解释一下字节和兆的关系。

1兆(MB) = 1024 × 1024 = 1,048,576 字节, 也就是2的20次方。

那么刚才这张图片是多少M呢?

一张尺寸为900×600的RGB图片占的内存大小:

900 × 600 × 3 = 1,620,000 字节(bytes) = 1.582 M

那么,刚才的那四种方案,好像是在做这个事情:将数据在字符和字节之间相互转换,并且尝试使用不同的编码。

那么字符又有哪些类别呢?

字符编码有:ASCII,Unicode 、UTF-8 和 base64等。

base64编码是用来解决把不可打印的内容塞进可打印内容的需求的。比如把图片存到数据库,图片数据归根到底还是一堆二进制串(总不能把这些二进制串直接存到数据库吧),用base64编码后的显示成的字符串就大大缩小的长度,可以存到数据库。

寻寻觅觅,后来终于发现,这种乱码的符号“���������”就是因为在字节和字符之间相互转换的过程中出现的,调查发现,我使用的npm包“request-promise”依赖“request”包,而正是后者的官网上隐藏着一段很深的话(坑):

(Note: if you expect binary data, you should set encoding: null.) 注意:如果你需要二进制数据(图片就属于这一类别),你应该将编码格式设成null。

然后,按照官网文档的建议,我设置了下,奇迹发生了。

代码仅仅增加了一段:

const options = { method: 'POST', uri: getUnlimitedUrl, encoding: null, body: { // scene: '', scene: 'id=160', page: 'pages/index/index' json: true // Automatically stringifies the body to JSONyaml

返回的数据就变成如下:

alt
  • utf8编码是常用的字符编码,它向下兼容ascii编码。并不是所有的 byte串 都能成功解码成人们能识别的 chat串,它是有解码算法(参考wiki),所以我们像���\u001d�)u�m\u001f�\u001a�͸��E这样常见的乱码是由于解码出错造成的。
  • 对于不能识别的byte串会解码成�,重点是�这货竟然有相应的utf8编码,编码为0xFFFD。这里有个关键点,很多byte串是无法正确解码的,但他们都会用�表示,而�字符又只有一种编码,所以对二进制数据如:图片,视频等,通过utf8编码并保存到变量后,是无法通过utf8原样解码成原来二进制的。
  • binary编码,也就是二进制编码,通常通过consle打印,为了“好看”会打印成16进制。
  • Base64是一种基于64个可打印字符来表示二进制数据的表示方法。对于我通常会用于将图片转换成data URLs,为了减少请求,或充分利用localStorage等。

自定义相机代码(感兴趣的用户可以测试下,这段代码在微信iOS端内的浏览器不支持):

<script> class SimpleCamera extends HTMLElement { constructor() { super(); connectedCallback() { const shadow = this.attachShadow({ mode: 'open' this.videoElement = document.createElement('video') this.canvasElement = document.createElement('canvas') this.videoElement.setAttribute('playsinline', true) this.canvasElement.style.display = 'none' shadow.appendChild(this.videoElement) shadow.appendChild(this.canvasElement) open(constraints) { console.log(navigator); return navigator.mediaDevices.getUserMedia(constraints).then((mediaStream) => { this.videoElement.srcObject = mediaStream console.log(mediaStream) this.videoElement.onloadedmetadata = (e) => { this.videoElement.play() _drawImage() { const imageWidth = this.videoElement.videoWidth const imageHeight = this.videoElement.videoHeight const context = this.canvasElement.getContext('2d') this.canvasElement.width = imageWidth this.canvasElement.height = imageHeight context.drawImage(this.videoElement, 0, 0, imageWidth, imageHeight) return { imageHeight, imageWidth takeBlobPhoto() { const { imageHeight, imageWidth } = this._drawImage() this.canvasElement.style.display = "block" const card = document.createElement('div') card.classList.add('card') document.querySelector('.wrapper').appendChild(card) card.appendChild(this.canvasElement) return new Promise((resolve, reject) => { this.canvasElement.toBlob((blob) => { resolve({ blob, imageHeight, imageWidth takeBase64Photo({ type, quality type: 'png', quality: 1 const { imageHeight, imageWidth } = this._drawImage() const base64 = this.canvasElement.toDataURL('image/' + type, quality) this.canvasElement.style.display = "block" const card = document.createElement('div') card.classList.add('card') document.querySelector('.wrapper').appendChild(card) card.appendChild(this.canvasElement) return { base64, imageHeight, imageWidth customElements.define('simple-camera', SimpleCamera)</script><div class="wrapper"> <div class="card"> <simple-camera></simple-camera> <div class="active"> <button id="btnBlobPhoto">Take Blob</button> <button id="btnBase64Photo">Take Base64</button> </div> </div></div><script> (async function() { const camera = document.querySelector('simple-camera') const btnBlobPhoto = document.querySelector('#btnBlobPhoto') const btnBase64Photo = document.querySelector('#btnBase64Photo') await camera.open({ video: { facingMode: 'user' btnBlobPhoto.addEventListener('click', async event => { const photo = await camera.takeBlobPhoto() btnBase64Photo.addEventListener('click', async event => { const photo = camera.takeBase64Photo({ type: 'jpeg', quality: 0.8</script>xml

https://segmentfault.com/a/1190000002787763

https://nodejs.org/api/buffer.html#buffer_class_method_buffer_from_array

https://www.jianshu.com/p/1af904e9a6e4

http://www.ruanyifeng.com/blog/2008/06/base64.html

http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

https://blog.csdn.net/charleslei/article/details/50993861

https://github.com/request/request#readme

https://zh.wikipedia.org/wiki/UTF-8#UTF-8%E7%9A%84%E7%B7%A8%E7%A2%BC%E6%96%B9%E5%BC%8F

https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.getUnlimited.html

本文链接:https://www.daguanren.cc/post/wxacode_saved_problem.html

-- EOF --

作者 daguanren

发表于 2019-07-29 18:30:15

,添加在分类 技术分享 Node.js

,并被添加「 npm Node.js 」标签

,最后修改于 2019-07-30 16:00:26

版权归本网站的作者所有,转载请注明出处,谢谢大家。 问题咨询和商务合作请发送邮件至[email protected]


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK