1、接口測試號申請
2、接口信息配置
由于本人并沒有錢買服務(wù)器,所以只能用內(nèi)網(wǎng)穿透工具,這里推薦一下 Sunny-Ngrok 支持自定義域名,官網(wǎng)上也有使用教程,對于微信公眾號開發(fā)來說還是蠻不錯的。有了域名我們就可以填寫下面的接口信息了。
2.1 驗證消息來自于微信服務(wù)器
? 在開發(fā)具體的功能之前,我們需要先進行token驗證,驗證消息確實來自于微信服務(wù)器,驗證方法是提交接口信息配置時,微信服務(wù)器會發(fā)送一個get請求到我們的服務(wù)器上,get請求攜帶signature, timestamp, nonce, echostr參數(shù)。通過檢驗signature對請求進行校驗。若確認此次get請求來自微信服務(wù)器,就原樣返回echostr參數(shù)內(nèi)容。
校驗流程:
- 將token、timestamp、nonce三個參數(shù)進行字典序排序
- 將三個參數(shù)字符串拼接成一個字符串進行sha1加密
- 將加密后的字符串與signature對比,相等則表示請求來自于微信服務(wù)器
項目結(jié)構(gòu):
project
|——package.json
|——app.js
|——router.js
|——config.js
|——controller
| └── authorize.js
|——node_modules
|——service
| └── authorize.js
|——utils
| └── http.js
| └── answer.js
app.js啟動文件router.js配置URL路由規(guī)則config.js存放appid,appsecret,token等信息controller/**用于解析用戶的輸入,返回處理的結(jié)果service/**編寫具體的業(yè)務(wù)邏輯-
utils/**存放工具函數(shù)啟動文件
app.js
const Koa = require('koa')
const app = new Koa()
const router = require('./router')
app.use(router.routes())
.use(router.allowedMethods())
app.listen(8888)
路由配置文件router.js
const Router = require('koa-router')
const router = new Router()
const authorize = require('./controller/authorize')
router.get('/authorize', authorize.config) // 校驗token路徑
module.exports = router
接收請求和返回結(jié)果文件controller/authorize.js
const authorize = require('../service/authorize') // 處理具體的驗證邏輯
const config = async (ctx) => {
let query = ctx.query
let result = await authorize._config(query)
ctx.body = result
}
module.exports = {
config
}
處理業(yè)務(wù)邏輯文件service/authorize.js
const config = require('../config')
const crypto = require('crypto')
const _config = async (query) => {
let signature = query.signature
let timestamp = query.timestamp
let echostr = query.echostr
let nonce = query.nonce
// 字典排序
let arr = [config.token, timestamp, nonce].sort()
// sha1加密
let str = arr.join('')
let hashCode = crypto.createHash('sha1')
let result = hashCode.update(str).digest('hex')
// 對比后返回結(jié)果
if (result === signature) {
return echostr
} else {
return ''
}
}
module.exports = {
_config
}
2.2 自動回復(fù)文本消息
? 當用戶發(fā)送消息給公眾號時(或某些特定的用戶操作引發(fā)的事件推送時),會產(chǎn)生一個POST請求,開發(fā)者可以特定XML結(jié)構(gòu),來對該消息進行響應(yīng)(現(xiàn)支持回復(fù)文本、圖片、圖文、語音、視頻、音樂)。在這里我們來實現(xiàn)以下最簡單的回復(fù)文本消息,用戶發(fā)什么,我們回復(fù)相同的內(nèi)容。
路由配置文件router.js
router.post('/authorize', authorize.handleMessage)
接收請求和返回結(jié)果文件controller/authorize.js
const handleMessage = async (ctx) => {
let message = await authorize.handleMessage(ctx) // 處理具體的業(yè)務(wù)邏輯
ctx.body = message
}
處理業(yè)務(wù)邏輯文件service/authorize.js
? 在這里,我們用到了兩個工具函數(shù),utils/answer.js 是消息回復(fù)模板,返回要回復(fù)的xml數(shù)據(jù),utils/xmlTool.js 用來解析xml數(shù)據(jù)。
const xmlTool = require('../utils/xmlTool')
const answer = require('../utils/answer')
const getRawBody = require('raw-body')
const handleMessage = async (ctx) => {
let xml = await getRawBody(ctx.req, {
length: ctx.request.length,
limit: '1mb',
encoding: ctx.request.charset || 'utf-8'
});
// 將xml數(shù)據(jù)轉(zhuǎn)化為json格式的數(shù)據(jù)
let result = await xmlTool.parseXML(xml)
// 格式化數(shù)據(jù)
let formatted = await xmlTool.formatMessage(result.xml)
// 判斷消息的類型,如果是文本消息則返回相同的內(nèi)容
if (formatted.MsgType === 'text') {
return answer.text(formatted)
} else {
return 'success'
}
}
工具函數(shù)
// xmlTool.js
const xml2js = require('xml2js');
const parseXML = async (xml) => {
return new Promise((resolve, reject) => {
xml2js.parseString(xml, {trim: true}, function (err, obj) {
if (err) {
return reject(err);
}
resolve(obj);
});
});
};
const formatMessage = (result) => {
let message = {};
if (typeof result === 'object') {
for (let key in result) {
if (!(result[key] instanceof Array) || result[key].length === 0) {
continue;
}
if (result[key].length === 1) {
let val = result[key][0];
if (typeof val === 'object') {
message[key] = formatMessage(val);
} else {
message[key] = (val || '').trim();
}
} else {
message[key] = result[key].map(function (item) {
return formatMessage(item);
});
}
}
}
return message;
};
module.exports = {
parseXML,
formatMessage
};
// answer.js
const ejs = require('ejs')
const messageTpl = '<xml>\n' +
'<ToUserName><![CDATA[<%-toUserName%>]]></ToUserName>' +
'<FromUserName><![CDATA[<%-fromUserName%>]]></FromUserName>' +
'<CreateTime><%=createTime%></CreateTime>' +
'<MsgType><![CDATA[<%=msgType%>]]></MsgType>' +
'<Content><![CDATA[<%-content%>]]></Content>' +
'</xml>';
const answer = {
text: function (message) {
let reply = {
toUserName: message.FromUserName,
fromUserName: message.ToUserName,
createTime: new Date().getTime(),
msgType: 'text',
content: message.Content
};
let output = ejs.render(messageTpl, reply);
return output;
}
}
module.exports = answer