使用NodeJs作為微信公眾號后台服務器
下面演示代碼的源碼地址 https://gitee.com/szxio/weChatServer
申請測試公眾號
首先登錄微信公眾平台,選擇自己的公眾號登錄。登錄成功后點擊開發者工具,選擇公眾平台測試賬號
點擊進去后我們可以申請一個測試用的公眾號,可以體驗所有高級接口,這里我們要配置一個線上的接口地址,在驗證 Tonken,和收發消息時微信都會請求我們配置的地址,這里推薦一個好用的內網穿透工具,可以把我們本地的項目地址映射到外網上,方便我們調試
這里我生成的線上地址是 http://songzx.ngrok2.xiaomiqiu.cn/,下面我們會用這個地址作為我們的公眾號的接口配置地址
實現Tonken驗證
首先新建一個空白的 node 項目
npm init -y
接着安裝一些常用的依賴
npm install express
接在在項目根路徑下新建 index.js
,初始代碼如下
const express = require("express")
const app = express()
app.get("/",(req,res)=>{
res.send('Hello World')
})
app.listen(8088,()=>{
console.log("running 127.0.0.1:8088");
})
然后啟動項目並用瀏覽器訪問 127.0.0.1:8088
可以看到如下結果,表示服務啟動成功
現在我們實現驗證 tonken
的邏輯
首先安裝如下依賴,用作加密處理
npm install crypto
然后新建 util
和 router
兩個文件夾,分別放置我們的統一的方法和普通請求方法
然后新建 util -> validateToken.js
文件,代碼如下,這個方法專門用來驗證微信傳遞過來的 Tonken
var crypto = require("crypto");
// 加密方法
function sha1(str) {
var md5sum = crypto.createHash("sha1");
md5sum.update(str);
str = md5sum.digest("hex");
return str;
}
// 驗證tonken
function validateToken(req) {
return new Promise((resolve, reject) => {
let query = req.query;
let signature = query.signature;
let echostr = query["echostr"];
let timestamp = query["timestamp"];
let nonce = query["nonce"];
let oriArray = new Array();
oriArray[0] = nonce;
oriArray[1] = timestamp;
oriArray[2] = "admin123"; // 這里是在公眾號接口配置信息里面填寫的Token
oriArray.sort();
let original = oriArray.join("");
let scyptoString = sha1(original);
if (signature == scyptoString) {
// 驗證通過,返回 echostr
resolve(echostr);
} else {
reject(false);
}
});
}
// 導出驗證 Tonken 的發放
module.exports = validateToken;
然后新建 router -> weChat.js
文件,這個文件專門用來處理微信發送過來的請求,在這個文件中編寫如下代碼
const express = require("express");
const router = express.Router(); // 配置路由模塊
const validateToken = require("../util/validateToken");
// get請求驗證tonken有效性
router.get("/", (req, res) => {
validateToken(req).then((t) => {
res.send(t);
});
});
// 導出 router
module.exports = router;
最后修改一下index.js
文件,引入我們新建的 router.js
文件
const express = require("express");
const app = express();
const path = require("path");
const weChat = require(path.resolve(__dirname, "./router/weChat"));
app.use(weChat);
app.listen(8088, () => {
console.log("running 127.0.0.1:8088");
});
現在我們去微信公眾號配置頁面中測試一下
頁面中彈出 配置成功 就表示我們驗證 Tonken
的業務已經完成了
獲取Tonken並定時刷新
微信中獲取 Tonken
要發送一個 get 請求來獲取,並且這個 Tonken
有過期時間,我們需要自己保存這個 Tonken
並定時刷新,以保證 Tonken
有效性
接口調用說明
- 請求方式: GET
- 請求地址:
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
既然要用到請求,我們安裝一個 axios
用來發送請求
npm install axios
然后在根目錄新建 public -> tonken.json
,用來存放我們獲取到的 tonken
,也是對 tonken
的一種持久化存儲方式,json
文件內容為空即可
接着新建 util -> tonkenConfig.js
文件,代碼如下
const fs = require("fs");
const path = require("path");
const http = require("axios");
const fileUrl = path.resolve(__dirname, "../public/tonken.json");
const APPID = "wx2188729b190d357d"; // 測試號的 APPID
const APPSECRET = "d976b0e6262b829ba003e9a24032447c"; // 測試號的 APPSECRET
let INTERTIME = (7200 - 60) * 1000; // 設置一個默認的定期獲取tonken的時間
// 保存Tonken
function setTonken() {
return new Promise((resolve, reject) => {
http
.get(
`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}`
)
.then((res) => {
// 更新tonken的過期時間,每隔這個時間重新獲取一次tonken
INTERTIME = (res.data.expires_in - 60) * 1000;
// 獲取到Tonken后保存到json文件中
fs.writeFile(
fileUrl,
JSON.stringify({
tonken: res.data.access_token,
}),
() => {
// 通知外界Tonken獲取成功
resolve();
}
);
});
});
}
// 定時獲取Tonken
function timingSetTonken() {
// 定時刷新tonken
setInterval(() => {
setTonken();
}, INTERTIME);
}
// 獲取Tonken
function getTonken() {
return new Promise((resolve, reject) => {
// 從json中讀取保存的Tonken
fs.readFile(fileUrl, (err, data) => {
// 返回獲取到的tonken
resolve(JSON.parse(data).tonken);
});
});
}
// 導出封裝好的方法
module.exports = {
setTonken, // 更新tonken
getTonken, // 返回獲取到的tonken
timingSetTonken, // 定時更新tonken
};
然后在 router -> weChat.js
中引入 tonkenConfig.js
const express = require("express");
const router = express.Router(); // 配置路由模塊
const validateToken = require("../util/validateToken");
const { setTonken, timingSetTonken } = require("../util/tonkenConfig");
// 項目啟動后自動執行獲取tonken的方法
setTonken().then(() => {
// tonken 獲取成功后開始定時刷新tonken操作
timingSetTonken();
});
// get請求驗證tonken有效性
router.get("/", (req, res) => {
validateToken(req).then((t) => {
res.send(t);
});
});
// 導出 router
module.exports = router;
此時我們在啟動項目后會自動調用一下獲取 tonken
的接口,然后從接口中獲取到一個過期時間,微信返回的過期時間是以秒為單位,減去60秒是為了下一次tonken
時與這次tonken
之間的平滑過渡,之后每隔這個時間會重新獲取一次tonken
我們將這個tonken
寫入到了一個json
文件中,我們可以在任何文件中通過如下方法獲取tonken
const { getTonken } = require("./util/tonkenConfig");
// 調用封裝好的獲取token方法
getTonken().then((tonken) => {
console.log(tonken); // 45_7k55HHRaYxM4MkD4aREraHZpgdjmT......
});
接收微信消息並回復
簡單說就是:我們在微信公眾號中發送消息后,微信會發送一個 post
請求給我們上面配置的地址,參數時一段 xml
文本,我們需要解析這個 xml
,並按照微信指定的格式回復一個 xml
格式的字符串,注意是回復 xml 格式的字符串
首先安裝依賴,用來解析post
請求中的xml
參數
npm install express-xml-bodyparser
然后在 index.js
文件中引用並配置中間件
const express = require("express");
const app = express();
const path = require("path");
const weChat = require(path.resolve(__dirname, "./router/weChat"));
const xmlparser = require('express-xml-bodyparser'); // 解析 xml
app.use(express.json());
app.use(express.urlencoded());
app.use(xmlparser());
app.use(weChat);
app.listen(8088, () => {
console.log("running 127.0.0.1:8088");
});
然后在 weChat.js
中添加一個 post
請求,打印一下看看微信給我們發過來的是什么東西
// post請求處理微信發送過來的消息
router.post("/", (req, res) => {
console.log(req.body);
res.send("");
});
重啟項目,我們往微信公眾號中隨便發送一個消息
解析后的參數如下
{
xml: {
tousername: [ 'gh_a0f004c20d2b' ],
fromusername: [ 'olttN6WJOYe-lTysV8_tsnZ7-HMQ' ],
createtime: [ '1621416487' ],
msgtype: [ 'text' ],
content: [ 'hello' ],
msgid: [ '23213103466653274' ]
}
}
拿到參數后我們可以根據參數中的 msgtype
判斷傳遞過來的消息類型,以及 content
是消息內容,獲取到了參數,接下要做的就是根據消息回復內容了
下面是一個回復消息的模板代碼,可以很方便的幫助我們生成指定的 xml
格式的字符串
// 回復文本消息
exports.textMessage = function (message) {
var createTime = new Date().getTime();
return `<xml>
<ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
<FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
<CreateTime>${createTime}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[${message.reply}]]></Content>
</xml>`;
};
// 回復圖片消息
exports.imageMessage = function (message) {
var createTime = new Date().getTime();
return `<xml>
<ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
<FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
<CreateTime>${createTime}</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<Image>
<MediaId><![CDATA[${message.mediaId}]]></MediaId>
</Image>
</xml>`;
};
// 回復語音消息
exports.voiceMessage = function (message) {
var createTime = new Date().getTime();
return `<xml>
<ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
<FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
<CreateTime>${createTime}</CreateTime>
<MsgType><![CDATA[voice]]></MsgType>
<Voice>
<MediaId><![CDATA[${message.mediaId}]]></MediaId>
</Voice>
</xml>`;
};
// 回復視頻消息
exports.videoMessage = function (message) {
var createTime = new Date().getTime();
return `<xml>
<ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
<FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
<CreateTime>${createTime}</CreateTime>
<MsgType><![CDATA[video]]></MsgType>
<Video>
<MediaId><![CDATA[${message.mediaId}]]></MediaId>
<Title><![CDATA[${message.title}]]></Title>
<Description><![CDATA[${message.description}]]></Description>
</Video>
</xml>`;
};
// 回復圖文消息
exports.articleMessage = function (message) {
var createTime = new Date().getTime();
return `<xml>
<ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
<FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
<CreateTime>${createTime}</CreateTime>
<MsgType><![CDATA[news]]></MsgType>
<ArticleCount>${message.articles.length}</ArticleCount>
<Articles>
${message.articles
.map(
(article) =>
`<item><Title><![CDATA[${article.title}]]></Title>
<Description><![CDATA[${article.description}]]></Description>
<PicUrl><![CDATA[${article.img}]]></PicUrl>
<Url><![CDATA[${article.url}]]></Url></item>`
)
.join("")}
</Articles>
</xml>`;
};
在 weChat.js
中引入上面的模板,這里我把模板代碼放到了 util -> template.js
中,然后修改剛剛新建的 post 方法
// 引入消息模板
const template = require("../util/template");
// post請求處理微信發送過來的消息
router.post("/", (req, res) => {
let xml = req.body.xml;
let msgtype = xml.msgtype[0];
switch (msgtype) {
case "text":
// 封裝要回復的消息參數
let message = {
FromUserName: xml.fromusername[0],
ToUserName: xml.tousername[0],
reply: "你好呀,我是通過代碼回復你的",
};
res.send(template.textMessage(message));
break;
default:
res.send(""); // 不是文本消息是默認響應一個空
break;
}
});
我們現在在發送消息試一試
我們看到公眾號已經可以回答我們了。