我们平常在使用微信内置浏览器访问第三方web网页的时候,通过右上角的三个点按钮可以把这个网页分享出去。一般分享出去的是网址链接的展现形式,但我们希望可以看出缩略图,标题,摘要,然后样式良好,这样给用户的体验也很好。
所幸,微信也是支持这种体验良好的分享方式,不过我们需要通过调用微信的JS-SDK来实现自定义分享效果。在这里,我主要讲的是:如何在nodejs后台环境下写一个生成微信签名的接口(nodejs+Koa 后台环境)。原谅小编只是苦逼的前端一枚,只会一点三脚猫nodejs功夫。>.<
- 前置工作
这里默认是已经做好诸如配置公众号、前端引入使用jdk等前置工作。如果还没有做或者不会的童鞋,请移步以下教程(这个教程非常的全面):
手把手带你使用JS-SDK自定义微信分享效果
- 问题与实现思路
首先,通过官方文档我们知道要生成signature ,我们必须要获取 accessToken 和jsapi_ticket 这两个从微信官方请求回来的东东,才能生成签名。但是问题并没有看上去的这么简单容易。感兴趣的可以查看下官方文档:
https://mp.weixin.qq.com/wiki?action=doc&id=mp1421141115&t=0.15697429783636763#buzhou3
所以必须要解决以下两个问题:
- 必须AppId和AppSecret请求accessToken,然后通过accessToken获取jsapi_ticket
- accessToke 每日限请求2000次,jsapi_ticket 每日限请求100000次,有效期都是7200秒(2小时)。
解决方法:
必须采用串行方法,首先获取到accessToken,再把accessToken作为参数来获取
jsapi_ticket。所以这里使用了Promise来实现串行方法。其他方法大体上采用全局方法来缓存signature 的值,前台每次请求生成签名接口时校验有没有过期。当前这里采用的是nodejs的fs模块写入一个本地的json文件,用来存储jsapi_ticket和上次请求的时间。当每次接口被请求时,读取这个json文件,校验过期。
具体实现代码实现:
const router = require("koa-router")();
const fs = require("fs");
const path = require("path");
const moment = require("moment");
const request = require("request");
var crypto = require("crypto");
const filePath = path.join(__dirname, "/data.json");
router.post("/wxconfig", async (ctx, next) => {
const req = ctx.request.body;
console.log(req);
let nowUrl = req.url;
// 定义两个函数返回Promise对象,用来组成串行,并最终获取到jsapi_ticket后最终处理成签名。
// 获取accessToken
const getToken = function() {
let p1 = new Promise((reslove, reject) => {
request(
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=这里是appid&secret=这里是appsecret",
function(error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body); // 注意返回的数据是一个纯字符串,要格式化处理
let token = JSON.parse(body).access_token;
if (token !== "") {
reslove(getJsapi(token));
}
}
}
);
});
return p1;
};
// 获取jsapi_ticket
const getJsapi = function(token) {
let p2 = new Promise((reslove, reject) => {
request(
"https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" +
token +
"&type=jsapi",
function(error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body); // 注意返回的数据是一个纯字符串,要格式化处理
// 存储当前 ticket
const ticketData = {
jsapi_ticket: "",
time: moment().format("YYYY/MM/DD HH:mm:ss")
};
if (JSON.parse(body).errcode === 0) {
// 如果成功获取到
ticketData.jsapi_ticket = JSON.parse(body).ticket;
/* 这里是将在这个同级目录下创建一个json文件用来存储jsapi_ticket,
和请求时间,用于下次接口被调用的过期校验。*/
fs.writeFile(filePath, JSON.stringify(ticketData), function(err) {
if (err) console.error(err);
console.log("写入ticketData的json文件成功!");
});
reslove(JSON.parse(body).ticket);
} else {
fs.writeFile(filePath, JSON.stringify(ticketData), function(err) {
if (err) console.error(err);
console.log("写入ticketData的json文件失败!");
});
}
}
}
);
});
return p2
.then(result => {
console.log(result);
// 在这里返回签名生成函数的结果给前台
let sendData = getSignature(nowUrl, result);
ctx.status = 200;
ctx.body = sendData;
})
.catch(err => {
console.log(err);
});
};
/* 这里是先判断存储json文件是否存在,若不存在或者文件存在但已过期,
就调用上方的串行函数,直接返回生成的签名给前台。若文件存在没过期,
直接使用json文件中的jsapi_ticket生成签名返回给前台使用。*/
if (fs.existsSync(filePath)) {
console.log("文件路径存在");
// 先读取
const jsapiData = JSON.parse(fs.readFileSync(filePath));
console.log(jsapiData);
// 先判断时间是否过期,若不过期传key,过期不传key
let t1 = jsapiData.time; // 数据,必须是2018/12-/01 12:09:04这种格式,否则Date对象无法转换
let dateBegin = new Date(t1); // 转化为Date对象的形式
let dateEnd = new Date(); //当前时间数据
let dateDiff = dateEnd.getTime() - dateBegin.getTime(); //时间差的毫秒数
// console.log(Math.floor(dateDiff / 1000))
if (Math.floor(dateDiff / 1000) > 7198) {
// 缓存时间超过有效期(过期)
sendData = await getToken();
} else {
// 不过期,调用签名生成函数生成结果直接ctx返回给前台
let signaData = await getSignature(nowUrl, jsapiData.jsapi_ticket);
ctx.status = 200;
ctx.body = signaData;
}
} else {
console.log("文件路径不存在");
sendData = await getToken();
}
});
// 生成签名函数
const getSignature = function(nowUrl, key) {
let noncestr = Math.random()
.toString(36)
.substr(2); // 随机字符串
let timestamp = moment().unix(); // 获取时间戳,数值类型
let jsapi_ticket = `jsapi_ticket=${key}&noncestr=${noncestr}×tamp=${timestamp}&url=${nowUrl}`;
// console.log(jsapi_ticket)
jsapi_ticket = getSha1(jsapi_ticket);
return {
noncestr: noncestr,
timestamp: timestamp,
signature: jsapi_ticket
};
};
/**
* @sha1加密模块 (加密固定,不可逆)
* @param str string 要加密的字符串
* @retrun string 加密后的字符串
* */
const getSha1 = function(str) {
var sha1 = crypto.createHash("sha1"); //定义加密方式:md5不可逆,此处的md5可以换成任意hash加密的方法名称;
sha1.update(str);
var res = sha1.digest("hex"); //加密后的值d
return res;
};
module.exports = router;
- 注意事项
- 微信的签名必须使用sha1的加密方式,而nodejs提供有通用的加密和哈希算法模块 crypto,直接引用即可。
- moment和request分别是第三方的 生成时间 和 ajax请求使用,所以需要使用npm install 一下,具体用法自行百度。
- 最好对照微信官方的校验工具,检查下处理的签名是否正确,毕竟accessToken和accessToken的获取每天都有测试和时间限制,最好不要频繁请求。官方的校验工具地址是: https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
- 前台传过来的页面地址必须是当前页面除去'#'hash部分的链接,必须格外注意,否则自定义分享会不成功。
还可以参考这篇教程来检查生成签名遇到的坑:
微信分享invalid signature签名错误的坑
后记
本篇笔记仅用于学习交流使用,如有谬误,请不吝指正,谢谢!