收藏
评论

小白的小程序二维码canvas绘制之旅--node.js

        要做一个分享到朋友圈的功能,但是微信没有相关的接口,只能通过将相关页面生成小程序码,然后生成图片,让用户保存到相册,再分享到朋友圈。于是,经过三天的折腾终于搞出来了,虽然不知道是不是正确的方法,还是分享出来,给同样没有经验的童鞋作参考。

首先是遇到的一些坑:

    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);
      }
    })
  },

基本就这些了吧。心累。先这样用用看吧,不知道会不会有其他问题=^=

最后一次编辑于  2018-09-22
赞 3
收藏

2 个评论

  • Anna Lu夜战八方哒叭将
    Anna Lu夜战八方哒叭将
    2018-11-08

    感谢分享!特别是后端获取access_token这一块!

    2018-11-08
    赞同 1
    回复
  • 星宇
    星宇
    2018-10-17

    如果只是临时生成用的用 wx.arrayBufferToBase64(res.data); 转成 base64  加上 data:image/png;base64, 用在线网站生成就好啦

    2018-10-17
    赞同
    回复 1
    • 明雨
      明雨
      2018-10-22

      嗯嗯,我这个是用于实时自动生成的

      2018-10-22
      回复