上傳開發(fā)代碼
- 項目本地啟動藕届,然后再 wechat 開發(fā)者工具上傳
- 檢查項目中配置的
appid
,檢查開發(fā)者工具里面對這個項目設(shè)置的appid
痛倚,兩者都要和 小程序的appid
相同,不然只能上傳部分代碼
生成二維碼
1. 獲取 access_token
根據(jù) 小程序官方文檔 可以知道怎么查詢,但還是有些地方需要注意
- 第一個參數(shù)
grant_type
的值固定為client_credential
上渴,對于開發(fā)者而言沒有什么特殊性 -
appid
和secret
都是需要登陸小程序,從微信公眾平臺上獲取 - 獲取到的
access_token
有效期是兩個小時喜颁,沒調(diào)用微信接口獲取一遍稠氮,之前獲取到的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);
// 過期時間半开,因網(wǎng)絡(luò)延遲等隔披,將實際過期時間提前10秒,以防止臨界點
const expireTime = Date.now() + (data.expires_in - 10) * 1000;
const token = data.access_token;
return { accessToken: token, expireTime };
};
2. 獲取二維碼
微信開發(fā)文檔中寂拆,提供三種奢米,二維碼的方式。
接口 A: 適用于需要的碼數(shù)量較少的業(yè)務(wù)場景
生成小程序碼纠永,可接受 path 參數(shù)較長鬓长,生成個數(shù)受限
接口 B:適用于需要的碼數(shù)量極多的業(yè)務(wù)場景
生成小程序碼,可接受頁面參數(shù)較短尝江,生成個數(shù)不受限涉波。
接口 C:適用于需要的碼數(shù)量較少的業(yè)務(wù)場景
生成二維碼,可接受 path 參數(shù)較長,生成個數(shù)受限
其中啤覆,接口 A 和接口 C 次數(shù)加起來苍日,共有 100,000 個。我在項目中是使用的接口 B窗声,所以用它來寫這個例子
好了相恃,下面就是獲取二維碼的正式內(nèi)容了,傳送門
這個獲取過程中其實還是不難的嫌佑,只是文檔有些地方?jīng)]有說明豆茫,我就說幾個我在開發(fā)過程中碰到的幾個常見錯誤
- access_token 的值不是放在
POST
請求的參數(shù)中,而是以GET
請求的方式拼接在鏈接的后面
https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN
- errcode":47001,"errmsg":"data format error"
這個錯誤有的是說 access_token 的值失效了屋摇。然而我碰到這個錯誤的原因不是這個揩魂,是由于我把
access_token
放入到了POST
請求的 body 中導(dǎo)致的,把access_token
從 body 刪除炮温,錯誤就解決了火脉。
- "errcode": 44002, "errmsg": "empty post data"
這個錯誤是我在
POST
請求的 body 中沒有傳遞值導(dǎo)致的,主要是沒有傳遞scene
的值導(dǎo)致柒啤。
3. 轉(zhuǎn)換成圖片
由于第二步中微信接口返回的數(shù)據(jù)并不是圖片的鏈接倦挂,而只是圖片的二進制內(nèi)容。當(dāng)時給我造成挺大的困擾担巩,因為這個接觸不多方援,只能通過搜索找到。下面說下我的解決過程
- 轉(zhuǎn)換成 base64 格式涛癌,然后把值傳遞給前端犯戏,前端把這個值放入到
imgage
標(biāo)簽的 src 位置就能顯示出來。
/**
* 生成小程序二維碼
* @param {*} originId 小程序的原始ID
* @param {*} body 小程序所需參數(shù)
*/
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 參數(shù)需要轉(zhuǎn)成 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 小程序所需參數(shù)
*/
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. 完整代碼
由于傳給前端的過程中出現(xiàn)了一些問題弃衍,最終選擇了第三種方法呀非。
第一種方法傳給前端后,能顯示镜盯,但是有時候會掃描失敗岸裙,而且開發(fā)者工具有效,真機調(diào)試實現(xiàn)速缆;
第二種方法降允,需要把圖片上傳到云服務(wù)器上,然后把鏈接返回前端激涤,過程比較麻煩;麻煩包括刪除上傳過的二維碼。
第三種方法比較方便倦踢,傳給小程序段顯示就行送滞。
// 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 } 請求參數(shù)
* params GET請求參數(shù)
*/
const get = (url, params, _options) => {
return request({ ..._options, params, url });
};
/**
* 封裝post請求
* @param { Object } 請求參數(shù)
* data POST請求請求參數(shù),對象形式
*/
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是否有效辱挥,檢查規(guī)則為當(dāng)前時間和過期時間進行對比
* @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);
// 過期時間犁嗅,因網(wǎng)絡(luò)延遲等,將實際過期時間提前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 小程序所需參數(shù)
*/
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;
};