基于nodejs的微信公眾號開發(fā)之接入與自動回復(fù)文本消息

1、接口測試號申請

進入微信公眾帳號測試號申請系統(tǒng)

2、接口信息配置

由于本人并沒有錢買服務(wù)器,所以只能用內(nèi)網(wǎng)穿透工具,這里推薦一下 Sunny-Ngrok 支持自定義域名,官網(wǎng)上也有使用教程,對于微信公眾號開發(fā)來說還是蠻不錯的。有了域名我們就可以填寫下面的接口信息了。

image

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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容