我们平常在使用微信内置浏览器访问第三方web网页的时候,通过右上角的三个点按钮可以把这个网页分享出去。一般分享出去的是网址链接的展现形式,但我们希望可以看出缩略图,标题,摘要,然后样式良好,这样给用户的体验也很好。
所幸,微信也是支持这种体验良好的分享方式,不过我们需要通过调用微信的JS-SDK来实现自定义分享效果。在这里,我主要讲的是:***如何在nodejs后台环境下写一个生成微信签名的接口(nodejs+Koa 后台环境)***。原谅小编只是苦逼的前端一枚,只会一点三脚猫nodejs功夫。>.<
- 前置工作
这里默认是已经做好诸如配置公众号、前端引入使用jdk等前置工作。如果还没有做或者不会的童鞋,请移步以下教程(这个教程非常的全面):
手把手带你使用JS-SDK自定义微信分享效果
- 问题与实现思路
首先,通过官方文档我们知道要生成signature ,我们必须要获取 accessToken 和jsapi_ticket 这两个从微信官方请求回来的东东,才能生成签名。但是问题并没有看上去的这么简单容易。感兴趣的可以查看下官方文档:
mp.weixin.qq.com/wiki?action…
所以必须要解决以下两个问题:
- 必须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后最终处理成签名。// 获取accessTokenconst 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_ticketconst 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); // 注意返回的数据是一个纯字符串,要格式化处理// 存储当前 ticketconst 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,过期不传keylet 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"); //加密后的值dreturn res;
};module.exports = router;复制代码
- 注意事项
- 微信的签名必须使用sha1的加密方式,而nodejs提供有通用的加密和哈希算法模块 crypto,直接引用即可。
- moment和request分别是第三方的 生成时间 和 ajax请求使用,所以需要使用npm install 一下,具体用法自行百度。
- 最好对照微信官方的校验工具,检查下处理的签名是否正确,毕竟accessToken和accessToken的获取每天都有测试和时间限制,最好不要频繁请求。官方的校验工具地址是: mp.weixin.qq.com/debug/cgi-b…
- 前台传过来的页面地址必须是当前页面除去'#'hash部分的链接,必须格外注意,否则自定义分享会不成功。
还可以参考这篇教程来检查生成签名遇到的坑:
微信分享invalid signature签名错误的坑
后记
本篇笔记仅用于学习交流使用,如有谬误,请不吝指正,谢谢!