申请测试公众号
首先登录微信公众平台,选择自己的公众号登录。登录成功后点击开发者工具,选择公众平台测试账号

点击进去后我们可以申请一个测试用的公众号,可以体验所有高级接口,这里我们要配置一个线上的接口地址,在验证 Tonken,和收发消息时微信都会请求我们配置的地址,这里推荐一个好用的内网穿透工具,可以把我们本地的项目地址映射到外网上,方便我们调试
小米球内网穿透工具
这里我生成的线上地址是 http://songzx.ngrok2.xiaomiqiu.cn/,下面我们会用这个地址作为我们的公众号的接口配置地址

实现Tonken验证
首先新建一个空白的 node 项目
接着安装一些常用的依赖
接在在项目根路径下新建 index.js
,初始代码如下
1 2 3 4 5 6 7 8 9 10
| 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
的逻辑
首先安装如下依赖,用作加密处理
然后新建 util
和 router
两个文件夹,分别放置我们的统一的方法和普通请求方法
然后新建 util -> validateToken.js
文件,代码如下,这个方法专门用来验证微信传递过来的 Tonken
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| var crypto = require("crypto");
function sha1(str) { var md5sum = crypto.createHash("sha1"); md5sum.update(str); str = md5sum.digest("hex"); return str; }
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"; oriArray.sort(); let original = oriArray.join(""); let scyptoString = sha1(original); if (signature == scyptoString) { resolve(echostr); } else { reject(false); } }); }
module.exports = validateToken;
|
然后新建 router -> weChat.js
文件,这个文件专门用来处理微信发送过来的请求,在这个文件中编写如下代码
1 2 3 4 5 6 7 8 9 10 11 12
| const express = require("express"); const router = express.Router(); const validateToken = require("../util/validateToken");
router.get("/", (req, res) => { validateToken(req).then((t) => { res.send(t); }); });
module.exports = router;
|
最后修改一下index.js
文件,引入我们新建的 router.js
文件
1 2 3 4 5 6 7 8 9 10
| 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
有效性
微信官方对于获取 Tonken 的描述
接口调用说明
- 请求方式: GET
- 请求地址:
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
既然要用到请求,我们安装一个 axios
用来发送请求
然后在根目录新建 public -> tonken.json
,用来存放我们获取到的 tonken
,也是对 tonken
的一种持久化存储方式,json
文件内容为空即可
接着新建 util -> tonkenConfig.js
文件,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| const fs = require("fs"); const path = require("path"); const http = require("axios"); const fileUrl = path.resolve(__dirname, "../public/tonken.json"); const APPID = "wx2188729b190d357d"; const APPSECRET = "d976b0e6262b829ba003e9a24032447c"; let INTERTIME = (7200 - 60) * 1000;
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) => { INTERTIME = (res.data.expires_in - 60) * 1000; fs.writeFile( fileUrl, JSON.stringify({ tonken: res.data.access_token, }), () => { resolve(); } ); }); }); }
function timingSetTonken() { setInterval(() => { setTonken(); }, INTERTIME); }
function getTonken() { return new Promise((resolve, reject) => { fs.readFile(fileUrl, (err, data) => { resolve(JSON.parse(data).tonken); }); }); }
module.exports = { setTonken, getTonken, timingSetTonken, };
|
然后在 router -> weChat.js
中引入 tonkenConfig.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const express = require("express"); const router = express.Router(); const validateToken = require("../util/validateToken"); const { setTonken, timingSetTonken } = require("../util/tonkenConfig");
setTonken().then(() => { timingSetTonken(); });
router.get("/", (req, res) => { validateToken(req).then((t) => { res.send(t); }); });
module.exports = router;
|
此时我们在启动项目后会自动调用一下获取 tonken
的接口,然后从接口中获取到一个过期时间,微信返回的过期时间是以秒为单位,减去60秒是为了下一次tonken
时与这次tonken
之间的平滑过渡,之后每隔这个时间会重新获取一次tonken
我们将这个tonken
写入到了一个json
文件中,我们可以在任何文件中通过如下方法获取tonken
1 2 3 4 5
| const { getTonken } = require("./util/tonkenConfig");
getTonken().then((tonken) => { console.log(tonken); });
|
接收微信消息并回复
官方对于接收消息的描述
简单说就是:我们在微信公众号中发送消息后,微信会发送一个 post
请求给我们上面配置的地址,参数时一段 xml
文本,我们需要解析这个 xml
,并按照微信指定的格式回复一个 xml
格式的字符串,注意是回复 xml 格式的字符串
首先安装依赖,用来解析post
请求中的xml
参数
1
| npm install express-xml-bodyparser
|
然后在 index.js
文件中引用并配置中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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'); 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
请求,打印一下看看微信给我们发过来的是什么东西
1 2 3 4 5
| router.post("/", (req, res) => { console.log(req.body); res.send(""); });
|
重启项目,我们往微信公众号中随便发送一个消息

解析后的参数如下
1 2 3 4 5 6 7 8 9 10
| { xml: { tousername: [ 'gh_a0f004c20d2b' ], fromusername: [ 'olttN6WJOYe-lTysV8_tsnZ7-HMQ' ], createtime: [ '1621416487' ], msgtype: [ 'text' ], content: [ 'hello' ], msgid: [ '23213103466653274' ] } }
|
拿到参数后我们可以根据参数中的 msgtype
判断传递过来的消息类型,以及 content
是消息内容,获取到了参数,接下要做的就是根据消息回复内容了
官方被动回复用户消息文档
下面是一个回复消息的模板代码,可以很方便的帮助我们生成指定的 xml
格式的字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| 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 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const template = require("../util/template");
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; } });
|
我们现在在发送消息试一试

我们看到公众号已经可以回答我们了。