nodejs后端写一个生成微信signature的接口

2,276 阅读5分钟

我们平常在使用微信内置浏览器访问第三方web网页的时候,通过右上角的三个点按钮可以把这个网页分享出去。一般分享出去的是网址链接的展现形式,但我们希望可以看出缩略图,标题,摘要,然后样式良好,这样给用户的体验也很好。

所幸,微信也是支持这种体验良好的分享方式,不过我们需要通过调用微信的JS-SDK来实现自定义分享效果。在这里,我主要讲的是:***如何在nodejs后台环境下写一个生成微信签名的接口(nodejs+Koa 后台环境)***。原谅小编只是苦逼的前端一枚,只会一点三脚猫nodejs功夫。>.<

  • 前置工作

这里默认是已经做好诸如配置公众号、前端引入使用jdk等前置工作。如果还没有做或者不会的童鞋,请移步以下教程(这个教程非常的全面):

手把手带你使用JS-SDK自定义微信分享效果

  • 问题与实现思路

首先,通过官方文档我们知道要生成signature ,我们必须要获取 accessToken 和jsapi_ticket 这两个从微信官方请求回来的东东,才能生成签名。但是问题并没有看上去的这么简单容易。感兴趣的可以查看下官方文档:

mp.weixin.qq.com/wiki?action…

所以必须要解决以下两个问题:

  1. 必须AppId和AppSecret请求accessToken,然后通过accessToken获取jsapi_ticket
  2. accessToke 每日限请求2000次,jsapi_ticket 每日限请求100000次,有效期都是7200秒(2小时)。

解决方法:

  1. 必须采用串行方法,首先获取到accessToken,再把accessToken作为参数来获取 jsapi_ticket。所以这里使用了Promise来实现串行方法。

  2. 其他方法大体上采用全局方法来缓存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}&timestamp=${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;

  • 注意事项
  1. 微信的签名必须使用sha1的加密方式,而nodejs提供有通用的加密和哈希算法模块 crypto,直接引用即可。
  2. moment和request分别是第三方的 生成时间 和 ajax请求使用,所以需要使用npm install 一下,具体用法自行百度。
  3. 最好对照微信官方的校验工具,检查下处理的签名是否正确,毕竟accessToken和accessToken的获取每天都有测试和时间限制,最好不要频繁请求。官方的校验工具地址是: mp.weixin.qq.com/debug/cgi-b…
  4. 前台传过来的页面地址必须是当前页面除去'#'hash部分的链接,必须格外注意,否则自定义分享会不成功。

还可以参考这篇教程来检查生成签名遇到的坑:

微信分享invalid signature签名错误的坑

后记

本篇笔记仅用于学习交流使用,如有谬误,请不吝指正,谢谢!