一、寫在前面的話
上一篇文章中,我們使用 Node.js 成功的實現了接入微信公眾平台功能。在這篇文章中,我們將實現微信公眾平台一個非常重要的參數 access_token ,它是公眾號的全局唯一接口調用憑據,公眾號調用各接口時都需使用 access_token。
在開始之前,讓我們先按捺住自己激動的心情、調整好呼吸,因為我們要將上一篇文章的代碼重新整理一下。一個好的項目結構,更能有助於我們理清業務邏輯以及將來維護代碼的便捷。OK!
二、整理項目結構
1.打開我們的項目,並在項目中添加文件夾,命名為 wechat ,如圖:
2.在 wechat 文件夾中添加文件並命名為 wechat.js。wechat.js 主要用於封裝開發微信公眾平台的所有方法。首先我們構建這個模塊的結構,代碼如下:
'use strict' //設置為嚴格模式
//構建 WeChat 對象 即 js中 函數就是對象
var WeChat = function(config){
//設置 WeChat 對象屬性 config
this.config = config;
//設置 WeChat 對象屬性 token
this.token = config.token;
}
//暴露可供外部訪問的接口
module.exports = WeChat;
嚴格模式:是在 ECMAScript 5 中引入的概念。嚴格模式是為 Javascript 定義了一種解析與執行模型。
module.exports :暴露接口用於外部操作。實際上我們定義模塊后,使用 node.js 的 require 引用時,node.js 會自動在我們定義的模塊外層加入以下代碼
/**
* exports module.exports 的一個簡短的引用
* require 用於引入模塊
* module 當前模塊的引用
* __filename 當前模塊的文件名
* __dirname 當前模塊的目錄名
*/
(function (exports, require, module, __filename, __dirname) {
//自定義模塊的代碼塊
})();
相信對於有過 javascript 開發經驗的同學,上面的代碼並不陌生。我們可以將它理解為一個閉包,是一個匿名方法的調用,避免污染全局變量。
小知識:
在上面的代碼中,除了我們所使用的 module.exports 對象,還有另一個用於暴露接口的 變量 exports (官方文檔將 module.exports 稱為對象,exports 稱為 屬性,我在這里也就這樣稱呼了),那么 module.exports 與 exports 有什么區別呢?
module.exports 對象是由模塊系統創建的,exports 變量是在模塊的文件級別作用域內有效的,它在模塊被執行前被賦於 module.exports 的值。——來自Node.js官方文檔
也就是說 exports 是 module.exports 的引用,而 module.exports 才是真正用於暴露接口的對象。 exports 賦值的所有屬性與方法都賦值給了 module.exports 對象。
如果 module.exports 與 exports 將值賦值給了相同的屬性,則按照賦值的先后順序,取最后一個賦值;如果我們給 module.exports 賦值的是一個對象,則會覆蓋 exports 的所有方法與屬性。
因此我們在暴露接口的使用上,如果只是單一屬性或方法的話,建議使用exports.屬性/方法,要是導出多個屬性或方法或使用對象構造方法,建議使用 module.exports。
具體詳解可以點擊查看該文章 -> Module.exports和exports的區別
3.為 WeChat 對象添加一個方法 auth,並將 app.js 中的驗證方法粘貼進去
'use strict' //設置為嚴格模式
const crypto = require('crypto'); //引入加密模塊
//構建 WeChat 對象 即 js中 函數就是對象
var WeChat = function(config){
//設置 WeChat 對象屬性 config
this.config = config;
//設置 WeChat 對象屬性 token
this.token = config.token;
}
/**
* 微信接入驗證
*/
WeChat.prototype.auth = function(req,res){
//1.獲取微信服務器Get請求的參數 signature、timestamp、nonce、echostr
var signature = req.query.signature,//微信加密簽名
timestamp = req.query.timestamp,//時間戳
nonce = req.query.nonce,//隨機數
echostr = req.query.echostr;//隨機字符串
//2.將token、timestamp、nonce三個參數進行字典序排序
var array = [this.token,timestamp,nonce];
array.sort();
//3.將三個參數字符串拼接成一個字符串進行sha1加密
var tempStr = array.join('');
const hashCode = crypto.createHash('sha1'); //創建加密類型
var resultCode = hashCode.update(tempStr,'utf8').digest('hex'); //對傳入的字符串進行加密
//4.開發者獲得加密后的字符串可與signature對比,標識該請求來源於微信
if(resultCode === signature){
res.send(echostr);
}else{
res.send('mismatch');
}
}
//暴露可供外部訪問的接口
module.exports = WeChat;
4.整理 app.js 文件的中的代碼,如下:
const express = require('express'), //express 框架
wechat = require('./wechat/wechat'),
config = require('./config');//引入配置文件
var app = express();//實例express框架
var wechatApp = new wechat(config); //實例wechat 模塊
//用於處理所有進入 3000 端口 get 的連接請求
app.get('/',function(req,res){
wechatApp.auth(req,res);
});
//監聽3000端口
app.listen(3000);
嗯!這樣代碼看着是不是舒服多了呢。
剩下的就是去微信公眾平台接入驗證了,在上一篇文章中有詳細的教程,這里我就不再演示了
三、access_token的獲取、存儲及更新
1.微信文檔步驟
在開始碼代碼之前,我們依然是先理清實現的思路,在開始編寫實現代碼。打開 微信幫助文檔 ,點擊左側菜單中的開始開發,點擊其子菜單獲取access_token,如圖:
通過上面的 API 的描述,我們總結出以下步驟:
- 實現 https Get 請求
- 獲取 access_token 並存儲 如果 當前 access_token 過期則更新
2.access_token的獲取、存儲及更新 代碼實現
整理好思路后我們就按照上一節的步驟去實現。通過幫助文檔我們將用於請求微信API 的請求地址與參數,存放到 config.json 文件。
其中 appid 與 secret 兩個參數 位於 微信公眾平台 左側菜單的基本配置中,如圖:
開發者密碼 點擊重置,用手機微信掃面二維碼后便可得到。config.json 代碼如下
{
"token":"wechat",
"appID":"wx154f********764da",
"appScrect":"59de4266*******8dbe9de4b798cd372",
"apiDomain":"https://api.weixin.qq.com/",
"apiURL":{
"accessTokenApi":"%scgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
}
}
由於微信 API 請求連接的域名是公用的,我們將它提出來,在請求地址中使用 %s(字符串) 占位符占位。
微信所有請求連接都是 https 協議,很幸運的是 Node.js 系統包中為我們提供了 https 的包,由於后面的請求會多次用到 https ,因此我們將它封裝為一個公用的方法,以便以后的使用,再次打開 wechat.js 在構造方法中,引入 https 模塊,並在構造函數內部添加 requestGet 方法
//用於處理 https Get請求方法
this.requestGet = function(url){
return new Promise(function(resolve,reject){
https.get(url,function(res){
var buffer = [],result = "";
//監聽 data 事件
res.on('data',function(data){
buffer.push(data);
});
//監聽 數據傳輸完成事件
res.on('end',function(){
result = Buffer.concat(buffer,buffer.length).toString('utf-8');
//將最后結果返回
resolve(result);
});
}).on('error',function(err){
reject(err);
});
});
}
提示:
npm 提供了很多用於請求的工具包,比如 request ( 安裝命令 npm install request ) 等。這里我只是用系統包去做請求處理。
由於 https 是異步請求的,我在這里面使用了 ES6 的 Promise 對象 。
完成了 requestGet方法后,我們的第1步驟也就完成了。下面開始第2步,獲取 access_token 並存儲 如果 當前 access_token 過期則更新。
在這之前我是想將 access_token 的存儲位置依然放在 config.json 文件中,由於 access_token 在更新后 需要將文件重寫,可能容易造成 config.json 文件的格式的紊亂,因此在 wechat 中重新創建一個 accessToken.json 文件用於存儲 access_token
{
"access_token":"",
"expires_time":0
}
其中 access_token 用於存儲 我們 GET 請求后access_token 的值,expires_time 用於存儲 access_token 的過期時間,保存為時間戳。
在 wechat.js 引入 fs 模塊用於操作文件、util 工具模塊用於處理占位符、 accessToken.json 文件
'use strict' //設置為嚴格模式
const crypto = require('crypto'), //引入加密模塊
https = require('https'), //引入 htts 模塊
util = require('util'), //引入 util 工具包
accessTokenJson = require('./access_token'); //引入本地存儲的 access_token
//構建 WeChat 對象 即 js中 函數就是對象
var WeChat = function(config){
//設置 WeChat 對象屬性 config
this.config = config;
//設置 WeChat 對象屬性 token
this.token = config.token;
//設置 WeChat 對象屬性 appID
this.appID = config.appID;
//設置 WeChat 對象屬性 appScrect
this.appScrect = config.appScrect;
//設置 WeChat 對象屬性 apiDomain
this.apiDomain = config.apiDomain;
//設置 WeChat 對象屬性 apiURL
this.apiURL = config.apiURL;
//用於處理 https Get請求方法
this.requestGet = function(url){
return new Promise(function(resolve,reject){
https.get(url,function(res){
var buffer = [],result = "";
//監聽 data 事件
res.on('data',function(data){
buffer.push(data);
});
//監聽 數據傳輸完成事件
res.on('end',function(){
result = Buffer.concat(buffer,buffer.length).toString('utf-8');
//將最后結果返回
resolve(result);
});
}).on('error',function(err){
reject(err);
});
});
}
}
在 wechat.js 添加獲取 access_token 的方法 getAccessToken
/**
* 獲取微信 access_token
*/
WeChat.prototype.getAccessToken = function(){
var that = this;
return new Promise(function(resolve,reject){
//獲取當前時間
var currentTime = new Date().getTime();
//格式化請求地址
var url = util.format(that.apiURL.accessTokenApi,that.apiDomain,that.appID,that.appScrect);
//判斷 本地存儲的 access_token 是否有效
if(accessTokenJson.access_token === "" || accessTokenJson.expires_time < currentTime){
that.requestGet(url).then(function(data){
var result = JSON.parse(data);
if(data.indexOf("errcode") < 0){
accessTokenJson.access_token = result.access_token;
accessTokenJson.expires_time = new Date().getTime() + (parseInt(result.expires_in) - 200) * 1000;
//更新本地存儲的
fs.writeFile('./wechat/access_token.json',JSON.stringify(accessTokenJson));
//將獲取后的 access_token 返回
resolve(accessTokenJson.access_token);
}else{
//將錯誤返回
resolve(result);
}
});
}else{
//將本地存儲的 access_token 返回
resolve(accessTokenJson.access_token);
}
});
}
在 app.js 中添加新的監聽鏈接用於測試 我們獲取的token
//用於請求獲取 access_token
app.get('/getAccessToken',function(req,res){
wechatApp.getAccessToken().then(function(data){
res.send(data);
});
});
這樣我們就大功告成了!
文章源代碼:https://github.com/SilenceHVK/wechatByNode 。對文章有不正確之處,請給予糾正。github源代碼請順手給個 Star,最后感謝您的閱讀。
文章目錄:
1.Node.js 接入微信公眾平台開發
2.Node.js access_token的獲取、存儲及更新
3.Node.js 自定義微信菜單
4.Node.js 微信消息管理