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;
  • 相关阅读:
    Importing data in R 1
    R Tidyverse dplyr包学习笔记2
    搭建PHP开发环境
    R parallel包学习笔记2
    测试笔试单选题(持续更新ing)
    http cookie管理中cookie police下部分参数含意
    全国省市县区域信息最新数据库脚本(mysql版本)
    Jmeter运行后出现乱码
    从自身谈在成熟与幼稚公司做测试的差别
    SQL查询中用到的函数
  • 原文地址:https://www.cnblogs.com/xiaoliwang/p/10196336.html
Copyright © 2011-2022 走看看