zoukankan      html  css  js  c++  java
  • 使用 nodeJs 开发微信公众号(设置自动回复消息)

    微信向第三方服务器发送请求时会降 signature 、timestamp、 nonce 、 openid(用户标识),发送内容会以 xml 的形式附加在请求中

    回复消息前提我们得拿到用户id , 用户发送内容等信息,用户发送内容格式参考微信官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453

    想要获取用户发送信息,需要从请求中获得 xml ,因此需要用到 raw-body(获得原生请求体)

    npm install raw-body --save

    接下来需要将xml从请求中分离并且格式化成json

    var getRawBody = require('raw-body')
    var contentType = require('content-type')
    var data = getRawBody(req, { length: req.headers['content-length'], limit: '1mb', encoding: contentType.parse(req).parameters.charset }, function(err, buf) { utils.formatMessage(buf.toString()).then(message => { //判断消息,做出回应 }) } )

    我将格式化 xml 的操作封装在 formatMessage

    var xml2js = require('xml2js')
    
    exports.formatMessage = function(xml) {
        return new Promise((resolve, reject) => {
            
            // 接收文本信息格式
            // <xml> <ToUserName><![CDATA[toUser]]></ToUserName>
            // <FromUserName><![CDATA[fromUser]]></FromUserName>
            // <CreateTime>1348831860</CreateTime>
            // <MsgType><![CDATA[text]]></MsgType>
            // <Content><![CDATA[this is a test]]></Content>
            // <MsgId>1234567890123456</MsgId></xml>
    
            xml2js.parseString(xml, function(err, content) {
                var result = content.xml
                var message = {};
                if (typeof result === 'object') {
                    var keys = Object.keys(result);
                    for (var i = 0; i < keys.length; i++) {
                        var key = keys[i];
                        var item = result[key];
                        if (!(item instanceof Array) || item.length === 0) continue;
                        if (item.length === 1) {
                            var val = item[0];
                            if (typeof val === 'object') message[key] = formatMessage(val);
                            else message[key] = (val || '').trim();
                        } else {
                            message[key] = [];
                            for (var j = 0, k = item.length; j < k; j++) message[key].push(formatMessage(item[j]));
                        }
                    }
                }
                resolve(message)
            })
        })
    }

    解析完成后我们可以拿到 FromUserName、MsgType 和 Content

    MsgType可能是 event(事件)或者是 text (文本)

    event类型有:subscribe,unsubscribe,LOCATION,CLICK,SCAN

    根据 content中发送的内容,我们可以进行判断,返回自定义消息回复

    微信规定我们返回的数据必须是xml格式的,格式参考:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140543

    因此在返回信息前需要拼接内容成指定xml格式,我将拼接方法封装在 template.js 文件中,使用时只要直接调用即可

    lib/template.js:

    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>`
    }

    自动回复整体流程:收到微信请求->校验是否来自微信->获取access_token->解析请求体xml->根据类型以及内容作出相应

    回复代码:

    var express = require('express')
    var router = express.Router()
    var getRawBody = require('raw-body')
    var contentType = require('content-type')
    var utils = require('../lib/utils.js')
    var template = require('../lib/template.js')
    
    // 微信官方请求回调接口
    router.all('/', function(req, res, next) {
        var data = getRawBody(req, {
            length: req.headers['content-length'],
            limit: '1mb',
            encoding: contentType.parse(req).parameters.charset
        }, function(err, buf) {
            if (err) return next(err)
            utils.formatMessage(buf.toString()).then(message => {
                if (message.MsgType == 'event') {
                    if (message.Event === 'subscribe') {
                        if (message.EventKey) {
                            console.log('扫描二维码关注:' + message.EventKey + ' ' + message.ticket);
                        }
                        message.reply = '终于等到你,还好我没放弃';
                    } else if (message.Event === 'unsubscribe') {
                        message.reply = '';
                        console.log(message.FromUserName + ' 悄悄地走了...');
                    } else if (message.Event === 'LOCATION') {
                        message.reply = '您上报的地理位置是:' + message.Latitude + ',' + message.Longitude;
                    } else if (message.Event === 'CLICK') {
                        message.reply = '您点击了菜单:' + message.EventKey;
                    } else if (message.Event === 'SCAN') {
                        message.reply = '关注后扫描二维码:' + message.Ticket;
                    }
                    res.send(template.textMessage(message))
                } else if (message.MsgType === 'text') {
                    var content = message.Content
                    if (content === '1') {
                        message.reply = '终于等到你'
                        res.send(template.textMessage(message))
                    } else if (content === '2') {
                        message.mediaId = '需要发送图片的媒体id'
                        res.send(template.imageMessage(message))
                    } else if (content === '3') {
                        message.articles = [{
                            title: '标题',
                            description: '描述',
                            picUrl: '图片路径,不需要事先上传',
                            url: '素材路径,素材需要事先上传'
                        }]
                        res.send(template.articleMessage(message))
                    } else {
                        message.reply = '你说的话:“' + content + '”,我听不懂呀'
                        res.send(template.textMessage(message))
                    }
                }
    
            })
        })
    
    });
    
    module.exports = router;
  • 相关阅读:
    【Azure 应用服务】在Azure App Service多实例的情况下,如何在应用中通过代码获取到实例名(Instance ID)呢?
    【Azure 应用服务】App Service For Windows 中如何设置代理实现前端静态文件和后端Java Spring Boot Jar包
    【Azure Developer】使用Azure Key Vault 的Key签名后,离线验证的一些参考资料
    【Azure Function】调试 VS Code Javascript Function本地不能运行,报错 Value cannot be null. (Parameter 'provider')问题
    【Azure 应用服务】App Service 使用Tomcat运行Java应用,如何设置前端网页缓存的相应参数呢(Xms512m Xmx1204m)?
    【Azure API 管理】APIM添加Logtoeventhub的策略后,一些相关APIM与Event Hub的问题
    【Azure API 管理】为调用APIM的请求启用Trace 调试APIM Policy的利器
    【Azure 事件中心】China Azure上是否有Kafka服务简答
    【Azure 应用服务】探索在Azure上设置禁止任何人访问App Service的默认域名(Default URL)
    【Azure 微服务】记一次错误的更新Service Fabric 证书而引发的集群崩溃而只能重建
  • 原文地址:https://www.cnblogs.com/xiaoliwang/p/10196336.html
Copyright © 2011-2022 走看看