微信小程序获取小程序码的过程记录

2018-11-19  本文已影响0人  心情后花园

上传开发代码

  1. 项目本地启动,然后再 wechat 开发者工具上传
  2. 检查项目中配置的 appid,检查开发者工具里面对这个项目设置的 appid,两者都要和 小程序的appid相同,不然只能上传部分代码

生成二维码

1. 获取 access_token

根据 小程序官方文档 可以知道怎么查询,但还是有些地方需要注意

  1. 第一个参数 grant_type 的值固定为 client_credential,对于开发者而言没有什么特殊性
  2. appidsecret 都是需要登陆小程序,从微信公众平台上获取
  3. 获取到的 access_token 有效期是两个小时,没调用微信接口获取一遍,之前获取到的 access_token 的值就失效,所以需要注意保存和刷新
  4. 代码如下
/**
 * 获取小程序的 access_token
 * @param {*} originId 小程序的originId
 */
const getAccessToken = async (appid, secret) => {
  const query = {
    appid,
    secret,
    grant_type: 'client_credential'
  };
  const { data } = await request.get(WECHAT_MINI_TOKEN_URL, query);
  // 过期时间,因网络延迟等,将实际过期时间提前10秒,以防止临界点
  const expireTime = Date.now() + (data.expires_in - 10) * 1000;
  const token = data.access_token;
  return { accessToken: token, expireTime };
};

2. 获取二维码

微信开发文档中,提供三种,二维码的方式。

接口 A: 适用于需要的码数量较少的业务场景

生成小程序码,可接受 path 参数较长,生成个数受限

接口 B:适用于需要的码数量极多的业务场景

生成小程序码,可接受页面参数较短,生成个数不受限。

接口 C:适用于需要的码数量较少的业务场景

生成二维码,可接受 path 参数较长,生成个数受限

其中,接口 A 和接口 C 次数加起来,共有 100,000 个。我在项目中是使用的接口 B,所以用它来写这个例子

好了,下面就是获取二维码的正式内容了,传送门

这个获取过程中其实还是不难的,只是文档有些地方没有说明,我就说几个我在开发过程中碰到的几个常见错误

  1. access_token 的值不是放在 POST 请求的参数中,而是以 GET 请求的方式拼接在链接的后面

https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN

  1. errcode":47001,"errmsg":"data format error"

这个错误有的是说 access_token 的值失效了。然而我碰到这个错误的原因不是这个,是由于我把 access_token 放入到了 POST 请求的 body 中导致的,把 access_token 从 body 删除,错误就解决了。

  1. "errcode": 44002, "errmsg": "empty post data"

这个错误是我在 POST 请求的 body 中没有传递值导致的,主要是没有传递 scene 的值导致。

3. 转换成图片

由于第二步中微信接口返回的数据并不是图片的链接,而只是图片的二进制内容。当时给我造成挺大的困扰,因为这个接触不多,只能通过搜索找到。下面说下我的解决过程

  1. 转换成 base64 格式,然后把值传递给前端,前端把这个值放入到 imgage 标签的 src 位置就能显示出来。
/**
 * 生成小程序二维码
 * @param {*} originId 小程序的原始ID
 * @param {*} body 小程序所需参数
 */
const generateQrCode = async (originId, body) => {
  // 返回的是个对象 { accessToken: 'token 值', expireTime: '过期时间' }
  const accessToken = await ensureAccessToken(originId); // 获取 access_token 的值,
  const { data } = await request.post(
    `${WECHAT_MINI_QRCODE_UNLIMIT_URL}?access_token=${accessToken.accessToken}`,
    body,
    {
      headers: {
        'Content-Type': 'application/json' // POST 参数需要转成 JSON 字符串,不支持 form 表单提交。
      },
      responseType: 'arraybuffer' // 重点
    }
  );
  
  const base64 = Buffer.from(data).toString('base64');
  return `data:image/jpg;base64,${base64}`;
};
  1. 写入本地文件
import fs from 'fs';

/**
 * 生成小程序二维码
 * @param {*} originId 小程序的原始ID
 * @param {*} body 小程序所需参数
 */
const generateQrCode = async (originId, body) => {
  const accessToken = await ensureAccessToken(originId);
  const { data } = await request.post(
    `${WECHAT_MINI_QRCODE_UNLIMIT_URL}?access_token=${accessToken.accessToken}`,
    body,
    {
      headers: {
        'Content-Type': 'application/json'
      },
      responseType: 'stream' // 重点
    }
  );
  data.pipe(fs.createWriteStream('./qrcode.png'));
  
  // 方式二:
  // responseType: 'arraybuffer'
  // fs.writeFile('./test_origin.jpg', data, err => {
  //   console.log('data err', err);
  // });
};
  1. 以流的形式传给前端
const generateQrCode = async (originId, body) => {
  const accessToken = await ensureAccessToken(originId);
  const { data } = await request.post(
    `${WECHAT_MINI_QRCODE_UNLIMIT_URL}?access_token=${accessToken.accessToken}`,
    body,
    {
      headers: {
        'Content-Type': 'application/json'
      },
      responseType: 'arraybuffer'
    }
  );
  return Buffer.from(data);
};

const getQrcode = async ctx => {
    const qrcode = generateQrCode(originId, {});
    ctx.type = 'png';
    ctx.body = qrcode;
};

4. 完整代码

由于传给前端的过程中出现了一些问题,最终选择了第三种方法。

第一种方法传给前端后,能显示,但是有时候会扫描失败,而且开发者工具有效,真机调试实现;

第二种方法,需要把图片上传到云服务器上,然后把链接返回前端,过程比较麻烦;麻烦包括删除上传过的二维码。

第三种方法比较方便,传给小程序段显示就行。

// request.js
import axios from 'axios';
import { merge } from 'lodash';

const request = async (_options, method = 'GET') => {
  const options = merge(
    {
      headers: {
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36'
      }
    },
    { ..._options },
    {
      method
    }
  );
  return axios(options);
};

/**
 * 封装get请求
 * @param { String } url 请求路径
 * @param { Object } 请求参数
 *  params GET请求参数
 */
const get = (url, params, _options) => {
  return request({ ..._options, params, url });
};

/**
 * 封装post请求
 * @param { Object } 请求参数
 *  data POST请求请求参数,对象形式
 */
const post = (url, data, _options) => {
  return request({ ..._options, data, url }, 'POST');
};

export { get, post, request };

// index.js

import mongoose from 'mongoose';
import * as request from '../util/request';

const WECHAT_MINI_TOKEN_URL = 'https://api.weixin.qq.com/cgi-bin/token';
const WECHAT_MINI_QRCODE_UNLIMIT_URL = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit';

/**
 * 检查AccessToken是否有效,检查规则为当前时间和过期时间进行对比
 * @param {Object} token token 对象
 */
const validAccessToken = (token) => {
  const { accessToken, expireTime } = token;
  return !!accessToken && Date.now() < expireTime;
};

/**
 * 获取小程序的 access_token
 * @param {*} originId 小程序的originId
 */
const getAccessToken = async (appid, secret) => {
  const query = {
    appid,
    secret,
    grant_type: 'client_credential'
  };
  const { data } = await request.get(WECHAT_MINI_TOKEN_URL, query);
  // 过期时间,因网络延迟等,将实际过期时间提前10秒,以防止临界点
  const expireTime = Date.now() + (data.expires_in - 10) * 1000;
  const token = data.access_token;
  return { accessToken: token, expireTime };
};

/**
 * 获取有效的 access_token
 */
const ensureAccessToken = async (originId) => {
  const WechatAccount = mongoose.model('WechatAccount');
  const account = await WechatAccount.findOne({ originId: 'gh_508456022339' });
  if (!account) {
    return null;
  }
  const { accessToken, appid, secret } = account;
  if (!validAccessToken(accessToken)) {
    const token = await getAccessToken(appid, secret);

    // 刷新 wechatAccount 的 token
    account.accessToken = token;
    await account.save();

    return token;
  }
  return accessToken;
};

/**
 * 生成小程序二维码
 * @param {*} originId 小程序的原始ID
 * @param {*} body 小程序所需参数
 */
const generateQrCode = async (originId, body) => {
  const accessToken = await ensureAccessToken(originId);
  const { data } = await request.post(
    `${WECHAT_MINI_QRCODE_UNLIMIT_URL}?access_token=${accessToken.accessToken}`,
    body,
    {
      headers: {
        'Content-Type': 'application/json'
      },
      responseType: 'arraybuffer'
    }
  );
  const base64 = Buffer.from(data).toString('base64');
  return Buffer.from(data);
};

// controller.js

/**
 * 生成二维码
 */
const getQrcode = async ctx => {
  const { content, path, width = 430 } = ctx.query;
  const { originId } = ctx.header;

  const params = {
    scene: _id,
    width,
    page
  };

  const data = await ctx.services.wechatUser.generateQrCode(originId, params);
  ctx.type = 'png';
  ctx.body = data;
};

上一篇下一篇

猜你喜欢

热点阅读