nodejs配置微信JSSDK

准备工作

微信公众号(订阅号||服务号)、备案过的域名(JS接口安全域名)、服务器(也可以用花生壳代替)

获取签名流程

  1. 通过公众号的appid&&appSecret获取accessToken

  2. 利用第一步的accessToken生成jsapi_ticket

  3. 再通过nonceStr(随机字符串)、jsapi_ticket、 timestamp(时间戳)、 url这四个字段组成的URL键值对格式的字符串作sha1加密(加密完就是我们想要的signature)

公众号配置

  1. 获取appid&&appSecret备用(开发=》基本配置)

  2. 设置获取access_token接口的IP白名单(此IP为跑wx_jssdk.js服务的服务器IP),如果未设置会报(invalid ip 122.234.183.166 ipv6 ::ffff:122.234.183.166, not in whitelist hint: [QgOCG.wgE-cCvqFa])错误(开发=》基本配置=》IP白名单)

  3. 设置JS接口安全域名(设置=》公众号设置=》功能设置=》JS接口安全域名), 将文件MP_verify_ua74c9a3LTB08ARW.txt上传此域名下的网站根目录,如果设置错误会报(invalid url domain)

nodejs代码实现

wx_jssdk.js

JAVASCRIPT
const Koa = require('koa'); 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 appid = "your appid"; const appSecret = "your appSecret"; const cors = require('koa2-cors'); //跨域处理 const app = new Koa(); app.use( cors({ origin: function (ctx) { //设置允许来自指定域名请求 if (ctx.url === '/test') { return '*'; // 允许来自所有域名请求 } return '*'; //只允许http://localhost:8080这个域名的请求 }, maxAge: 5, //指定本次预检请求的有效期,单位为秒。 credentials: true, //是否允许发送Cookie allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], //设置所允许的HTTP请求方法 allowHeaders: ['Content-Type', 'Authorization', 'Accept'], //设置服务器支持的所有头信息字段 exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'] //设置获取其他自定义字段 }) ); router.prefix('/jssdk') const filePath = path.join(__dirname, "/wxconfig.json"); router.get("/wxconfig", async (ctx, next) => { const req = ctx.request.url; let nowUrl = decodeURIComponent(req.split('url=')[1]); console.log(nowUrl) // 定义两个函数返回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(); } }); // 生成签名函数 /* 签名生成规则如下:参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1 = value1 & key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。 */ 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 { appid: appid, 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; }; app.use(router.routes()); /*启动路由*/ app.use(router.allowedMethods()); /* * router.allowedMethods()作用: 这是官方文档的推荐用法,我们可以 * 看到 router.allowedMethods()用在了路由匹配 router.routes()之后,所以在当所有 * 路由中间件最后调用.此时根据 ctx.status 设置 response 响应头 * */ app.listen(3003, () => { console.log('正在监听端口3003'); }); // module.exports = router;

package.json

JSON
{ "name": "node-service", "devDependencies": {}, "dependencies": { "koa": "^2.11.0", "koa-router": "^8.0.8", "koa2-cors": "^2.0.6", "moment": "^2.24.0", "request": "^2.88.2" } }

部署服务器

服务器安装NODE 、PM2,
将项目上传至服务器,
执行npm install
进程守护pm2 start node wx_jssdk.js

校验

微信文档:js-sdk说明文档

在线验证签名:微信 JS 接口签名校验工具

划重点

  1. 获取access_token接口时要在 公众号后台=》开发=》基本配置=》IP白名单 里添加访问的IP

  2. 一定要记得设置JS接口安全域名

  3. 服务器上跑node服务最好用进程守护,否则进程意外中断不再会重启

【END】

本文链接:

版权声明:本博客所有文章除声明转载外,均采用 BY-NC-SA 3.0 许可协议。转载请注明来自 Joubn's blog

阅读 74 | 发布于 2020-06-30
暂无评论