要做一个分享到朋友圈的功能,但是微信没有相关的接口,只能通过将相关页面生成小程序码,然后生成图片,让用户保存到相册,再分享到朋友圈。于是,经过三天的折腾终于搞出来了,虽然不知道是不是正确的方法,还是分享出来,给同样没有经验的童鞋作参考。
首先是遇到的一些坑:
1. 开始的时候直接在前端调用接口,但是发现 api.weixin.qq.com 添加到请求域名,因此只能从后端获取。
2. 后端的请求我不会,T^T,这个确实是我自己太菜了,我用的 node.js,搜索发现使用 var request = require('request') 就可以了。
3. 后端请求的时候,因为这是异步操作,导致微信接口还没有返回结果,我这边接口就已经返回小程序端了,这里需要 promise 搭配 await 来实现进程阻塞。
4. 获取到token之后,使用生成二维码的接口 getwxacodeunlimit,得到返回值是一堆乱码,我以为这是正常的数据,于是开始想办法处理这些数据。网上搜到的是用 pipe 和 fs 存到服务器,在从前端把图片从服务器下载下来,在canvas画,但是我用的腾讯云小程序方案,根本不知道图片存到了哪里,也不知道下载链接。所以我就尝试将乱码发送到前端,用 arrayBufferToBase64 处理,发现根本没有输出。。。最后折腾了好久,发现是最开始获取的时候,接受数据的编码格式不对,在 request 的请求参数 opts 里添加一句 :encoding: null ,就可以正确接受,结果是一个buffer变量,里面是多维数字数组。
5. 得到数据之后,我以为只要能在前端显示,就能在canvas里画了,于是就把buffer发送到前端,然后用arrayBufferToBase64 处理,再加上 "data:image/png;base64," 头,就可以作为 image 标签的 src 参数进行显示了,然而,这种 base64 图片,在开发工具上可以画在 canvas里,真机上却不行。官方不支持。。。。所以饶了一圈,又得回到原点。最后还是通过在后端,将得到的buffer上传到cos对象存储,再将存储地址返回到前端,前端通过wx.downloadFile将图片下载下来,再画到 canvas 上。
然后是一些要注意的点:
1. 真机获取二维码只能通过后端实现。
2. canvas画图是异步操作,需要延时后执行 canvasToTempFilePath
3. 除了B接口,另外两个是有个数限制的,要小心调用,用完就没了。
4. token调用也是有次数限制的,需要做处理。
5. canvas大小需要自己手动根据屏幕尺寸进行调整。
6. 我用的wafer 2 ,node.js后端,cos存储
7. 生成的二维码进入的是已经发布的版本,我的是已经有一个发布的版本的。
最后是代码(水平有限):
后端,放在server/controllers/getqrcode.js:
const { mysql } = require( '../qcloud' ) //腾讯云对象存储接口 const COS = require( 'cos-nodejs-sdk-v5' ) // 获取基础配置 const configs = require( '../config' ) //请求包 var request = require( 'request' ) // token有效时间 var period = 7200000; //小程序开发信息 const APPID = 'wxxxxxxxxxxxxxxxxxx' const APPSECRET = 'b3dxxxxxxxxxxxxxxxxxxxxxx' module.exports = async ctx => { //小程序传入的参数,path是页面路径放到page里,pptid是参数,放到scene里的 const pptid = ctx.query.pptid const path = ctx.query.path let result = '' let msg = 'init' let id = 1 let token = 'none' var urlpath = 'none' console.log( 'getqrcode\r\n' ) const getAccessToken = function () { //获取token var opts = { method: 'GET' , url: 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' + APPID + '&secret=' + APPSECRET, header: { 'content-type' : 'application/json' // 默认值 } } console.log( 'do get AccessToken!\r\n' ) return new Promise((resolve, reject) => { request.get(opts, function (err, response, body) { if (!err && response.statusCode == 200) { if (body !== 'null' ) { results = body; code = JSON.parse(results); if (code.access_token != null ) { let accessToken = code.access_token resolve(accessToken) } else { msg = 'joberror' console.log( 'job error:' + results); reject(results) } } else { msg = 'getfail' reject(msg) } } else { msg = 'netfail' reject(err) } }); }) }; const getqrc = function (token) { //请求二维码的参数 var postData = { page: path, width: 430, scene: pptid } var opts = { method: 'POST' , url: 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=' + token, body: JSON.stringify(postData), encoding: null , // responseType:"arraybuffer",//这个好像没用 } console.log( 'do get QRcode!\r\n' ) return new Promise((resolve, reject) => { request(opts, function (err, response, body) { if (!err && response.statusCode == 200) { if (body !== 'null' ) { result = body // console.log(body) msg = 'qrdone' resolve(body) } else { msg = 'qrgetfail' reject(msg) } } else { msg = 'qrnetfail' reject(err) } }) //.pipe(fs.createWriteStream('./temp2code.jpg')) }) }; const uploadimg= function (body){ //上传到cos //body就是上一步获取到的body //存到cos后自动就生成图片了 var cos = new COS({ // 必选参数 SecretId: configs.cos.SecretId, SecretKey: configs.cos.SecretKey, // 可选参数 FileParallelLimit: 3, // 控制文件上传并发数 ChunkParallelLimit: 8, // 控制单个文件下分片上传并发数,在同园区上传可以设置较大的并发数 ChunkSize: 1024 * 1024, // 控制分片大小,单位 B,在同园区上传可以设置较大的分片大小 }); return new Promise((resolve, reject) => { cos.putObject({ Bucket: configs.cos.Bucket, // Bucket 格式:test-1250000000 Region: configs.cos.region, Key: 'qrcode-' +pptid+ '.jpg' , /* 给文件起个名字 */ Body: body, /* 必须 */ }, function (err, data) { if (err) { reject(err) console.log(err); } else { resolve(data) // console.log(data); } }); }) } //查询数据库中的token是否需要更新 let res = await mysql( "systemSet" ).where({ id }).first() console.log( 'sql updated on:' + res.updatetime + '\r\n' ) var timestamp = Date.parse( new Date()); if (timestamp - Date.parse(res.updatetime) < period){ //不需要更新 msg= 'good' token= res.accesstoken } else { //token太久了,需要更新 try { const t = await getAccessToken() token = t //更新数据库中的token await mysql( "systemSet" ).update({ accesstoken: token }).where({ id }) msg = 'update' console.log(t.substring(0, 5) + '...done!\r\n' ) } catch (err) { console.log(err); } } //获取二维码数据 if (msg == 'good' || msg == 'update' ) { try { var q = await getqrc(token) // console.log(q) try { // 存储到cos,并返回地址 var p = await uploadimg(q) urlpath = 'https://' + p.Location // console.log(p) } catch (err) { console.log(err) } } catch (err) { var q = err console.log(err); } } else { var q = 'notoken' } //将地址返回到前端 ctx.state.data = urlpath console.log( 'finish\r\n' ) } |
小程序端代码(只贴获取二维码图片了):
getqrcode: function (){ let that = this wx.showLoading({ title: '图片正在生成中...' }) qcloud.request({ url: `${config.service.host}/weapp/getqrcode`, login: false , data: { pptid: that.data.pptid, path: 'pages/detail/detail' }, success(result) { //成功获取到了地址 console.log( 'Success get qrcode:' ,result) let res = result.data.data // var base64 = wx.arrayBufferToBase64(res.data); // console.log(res) // that.setData({ imgurlqr: "data:image/png;base64," + base64 }) //从cos下载图片 wx.downloadFile({ url: res, success: function (res0) { //获取到临时地址,进行canvas的绘制 that.drawCanv(res0.tempFilePath) }, fail: function (err) { console.log(err) } }) }, fail(error) { util.showModel( '请求失败' , error); console.log( 'request fail' , error); } }) }, |
基本就这些了吧。心累。先这样用用看吧,不知道会不会有其他问题=^=
感谢分享!特别是后端获取access_token这一块!
如果只是临时生成用的用 wx.arrayBufferToBase64(res.data); 转成 base64 加上 data:image/png;base64, 用在线网站生成就好啦
嗯嗯,我这个是用于实时自动生成的