微信小程序获取小程序码的过程记录
上传开发代码
- 项目本地启动,然后再 wechat 开发者工具上传
- 检查项目中配置的
appid
,检查开发者工具里面对这个项目设置的appid
,两者都要和 小程序的appid
相同,不然只能上传部分代码
生成二维码
1. 获取 access_token
根据 小程序官方文档 可以知道怎么查询,但还是有些地方需要注意
- 第一个参数
grant_type
的值固定为client_credential
,对于开发者而言没有什么特殊性 -
appid
和secret
都是需要登陆小程序,从微信公众平台上获取 - 获取到的
access_token
有效期是两个小时,没调用微信接口获取一遍,之前获取到的access_token
的值就失效,所以需要注意保存和刷新 - 代码如下
/**
* 获取小程序的 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,所以用它来写这个例子
好了,下面就是获取二维码的正式内容了,传送门
这个获取过程中其实还是不难的,只是文档有些地方没有说明,我就说几个我在开发过程中碰到的几个常见错误
- access_token 的值不是放在
POST
请求的参数中,而是以GET
请求的方式拼接在链接的后面
https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN
- errcode":47001,"errmsg":"data format error"
这个错误有的是说 access_token 的值失效了。然而我碰到这个错误的原因不是这个,是由于我把
access_token
放入到了POST
请求的 body 中导致的,把access_token
从 body 删除,错误就解决了。
- "errcode": 44002, "errmsg": "empty post data"
这个错误是我在
POST
请求的 body 中没有传递值导致的,主要是没有传递scene
的值导致。
3. 转换成图片
由于第二步中微信接口返回的数据并不是图片的链接,而只是图片的二进制内容。当时给我造成挺大的困扰,因为这个接触不多,只能通过搜索找到。下面说下我的解决过程
- 转换成 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}`;
};
- 写入本地文件
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);
// });
};
- 以流的形式传给前端
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;
};