Backend access to WeChat public account - automatic reply function - express

Table of contents

1. Configure the server and fill in relevant information

1-1) Solve the situation that port 80 is being occupied by the port of nginx service, and complete the server verification

2. Receive user's message

2-1) Add a method to listen to post requests in the express project to obtain user messages

2-2) Use xmljson to parse and process user messages

3. Reply to user's message - text reply

4. Reply to the user's content-picture

4-1) Upload pictures to the material library of the official account:

4-2) Get access-token

4-3) Obtain image resources in the official account (permanent material list)

4-4) Return a picture to the user as a reply


1. Configure the server and fill in relevant information

        The first step to access the WeChat public account is to log in to the official website of the WeChat public platform. On the development-basic settings page on the left navigation bar of the official website of the public platform, check the protocol to become a developer, click the "Modify Configuration" button, and fill in the server address (URL), Token and EncodingAESKey.

        The URL is the interface URL used by developers to receive WeChat messages and events. The Token can be filled in by the developer arbitrarily and used to generate a signature (the Token will be compared with the Token contained in the interface URL to verify the security). EncodingAESKey is manually filled in by the developer or randomly generated, and will be used as the encryption and decryption key for the message body.

         After filling in the above information, the WeChat server will send a GET request to the URL of the filled server address, and the request will carry the following parameters:

        The developer verifies the request through the verification methods below. If it is confirmed that the GET request comes from the WeChat server, the echostr parameter content will be returned as it is, and the access will be successful; otherwise, the access will fail. The encryption/verification process is as follows:

        1) Sort the token (that is, the token we set), timestamp, and nonce in lexicographical order;

        2) Concatenate the three parameter strings into one string for sha1 encryption;

        3) The encrypted string obtained by the developer can be compared with the signature to identify that the request comes from WeChat;

 

1-1) Solve the situation that port 80 is being occupied by the port of nginx service, and complete the server verification

        According to the verification methods and examples given in the official documents, complete the express verification service and deploy it:

const express = require('express');
const crypto = require('crypto');

app.get('/wechat-test', (req, res)=>{
    const token = 'xxx_xxx';
    let {signature, timestamp, nonce, echostr} = req.query;
    let arr = [nonce, timestamp, token];
    arr.sort();                         // 排序
    let str = arr.join('');             // 转成字符串
    let shanum = crypto.createHash('sha1');
    let mysignature = shanum.update(str).digest('hex');

    let result = (mysignature === signature);
    if(result){
        res.send(echostr);
    }else{
        res.send();
    }
});

app.listen(80, ()=>{
    console.log('80 listening');
})

        Fill in the http protocol, the server address of port 80. Deploy the above express server program to the server and try running:

cd 项目部署的目录下
pm2 start index.js --name wechat-test

        An error was found: EADDRINUSE, that is, the port 80 is already occupied. Check the usage of port 80:

sudo lsof -i :80

         It is found that nginx on port 80 is listening and providing services:

         In order not to destroy the original service of the main page, choose to use the request forwarding method, set the listening port of the express project to 3001 , and open the port in the console security group , and configure nginx to forward the access of 80 to port 3001 :

server{
    listen 80;

    ......

    # 请求转发到本地的3001端口进行处理
    location /wechat-test {
        proxy_pass http://localhost:3001;
    }
}

        A request for http://www.example.cn/wechat-test will be forwarded to http://www.example.cn:3001/wechat-test .

        Run the express service on the server again. After the operation is successful, submit the server configuration on WeChat. WeChat will get a request to the target URL for verification. After the verification is successful, it will display that the access is successful. Select to start the server configuration:

        

 

2. Receive user's message

        When an ordinary WeChat user sends a message to the public account, the WeChat server will send a post request to the URL address we filled in, and carry XML data (including the message entered by the user and some other information).

        a. Text message XML:

<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>
  <MsgDataId>xxxx</MsgDataId>
  <Idx>xxxx</Idx>
</xml>

        in:

toUserName Developer WeChat
FromUserName Sender account (openid)
CreateTime time the message was created
MsgType The type of the message, if it is text, it will be text
Content the content of the text message

        b. Picture message:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[image]]></MsgType>
  <PicUrl><![CDATA[this is a url]]></PicUrl>
  <MediaId><![CDATA[media_id]]></MediaId>
  <MsgId>1234567890123456</MsgId>
   <MsgDataId>xxxx</MsgDataId>
  <Idx>xxxx</Idx>
</xml>

        in:

PicUrl Image link generated by the system
MediaId Image message media id, you can call the get temporary material interface to pull data

2-1) Add a method to listen to post requests in the express project to obtain user messages

        Just add it to the original project file.

// 监听的路径要和填写的URL路径一致
app.post('/wechat-test',(req, res)=>{
    // 用来保存xml数据结果
    let data = '';

    // 接收xml数据
    req.on('data',(chunk)=>{
        data += chunk;
    })
    req.on('err',(err)=>{
        res.send('');
    })
    req.on('end',()=>{        // 数据接收完毕
        console.log('数据接收完毕\n', data);
        res.send();
    })
})

        Send a message in the official account: 1234. Use pm2 logs to view the operation log and view the output of the project:

         At this time, no response from the official account can be seen in the WeChat official account.

2-2) Use xmljson to parse and process user messages

         Download xmljson: npm i xmljson;

        Basic use, convert xml content into object form:

// 直接引入一个方法,将xml内容转换成对象
const toJson = require('xmljson').to_json;

app.post('/wechat-test',(req, res)=>{
    let data = '';
    req.on('data', chunk=>{
        data += chunk;
    })
    req.on('err',(err)=>{
        res.send('');
    })
    req.on('end',()=>{
        console.log('接收完毕:\n',data,'\n\n');
        toJson(data, (err, xmlObj)=>{
            if(err){
                console.log('转换错误');
                res.send('处理错误');
            }else{
                console.log('转换完成:\n', xmlObj);
                res.send('');
            }
        })
    })
})

        Send a message in the official account: hello.

 

         The converted object contains an xml object, and the content in the object is in the form of "xml tag name: tag body content". To get the user's message, we only need xmlObj.xml.Content.

 

3. Reply to user's message - text reply

        The content of the reply also needs to be in XML format, such as:

         Among them, the content in Content is the content of the reply message. Both ToUSerName and FromUserName must be carried.

        The following example will reply to the user: `Hi, ${message from the user}`;

app.post('/wechat-test',(req, res)=>{
    let data = '';
    req.on('data',(chunk)=>{
        data += chunk;
    }).on('end',()=>{
        toJson(data, (err, obj)=>{
            if(err){
                console.log('错误:',new Date().toLocaleString());
                res.send(`
                    <xml>
                        <ToUserName><![CDATA[${obj.xml.FromUserName}]]></ToUserName>
                        <FromUserName><![CDATA[${obj.xml.ToUserName}]]></FromUserName>
                        <CreateTime>${new Date().getTime()}</CreateTime>
                        <MsgType><![CDATA[text]]></MsgType>
                        <Content><![CDATA[处理错误]]></Content>
                    </xml> 
                `)
            }else{
                let ans = `
                    <xml>
                        <ToUserName><![CDATA[${obj.xml.FromUserName}]]></ToUserName>
                        <FromUserName><![CDATA[${obj.xml.ToUserName}]]></FromUserName>
                        <CreateTime>${new Date().getTime()}</CreateTime>
                        <MsgType><![CDATA[text]]></MsgType>
                        <Content><![CDATA[你好,${obj.xml.Content}]]></Content>
                    </xml> 
                `
                res.send(ans);
            }
        })
    })
})

         Send a message on the official account and check the reply:

         

        Note that the server needs to reply within 5s, otherwise the WeChat server will disconnect after 5s and resend the request. A total of 3 requests will be sent. If the server fails to reply on time, it will be processed as a timeout.

        If the server cannot guarantee a reply within 5 seconds, consider replying success directly, or return an empty string (no reply).

       If the server does not return the data within 5 seconds, or the returned data is not in xml format, WeChat will send a system prompt message to the user in the official account dialogue "This official account is temporarily unable to provide services, please try again later".

 

4. Reply to the user's content-picture

 4-1) Upload pictures to the material library of the official account:

 

4-2) Get access-token

         The access-token is required to obtain the material in the material library, and the following request can be sent to post the access-token:

GET  https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

        If you want to get access-token, fill in client_credential for grant_type;

        Appid and secret can be configured and obtained in Official Account - Basic Configuration:

         After the request is successful, there will be the following information in res.data, where expires_in is the valid time s:

Reference: WeChat Open Documentation

         

4-3) Obtain image resources in the official account (permanent material list)

         To get the list of permanent materials, you can send the following request:

post

https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=ACCESS_TOKEN

access-token 即刚刚获取到的access_token

需要携带的data:{
    "type":"image",  // 表示请求图片资源,还可以是视频(video)、语音 (voice)、图文(news)
    "offset":0,      // 从全部素材的该偏移位置开始返回,0表示从第一个素材 返回
    "count":5,       // 表示返回的数量在 1-n 之间,当前为 1-5
}

        An example:

// 处理图片类型的消息-返回固定的图片
function dealImgMsg(xmlObj, res){
    // 先获取 access_token
    axios.get('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${YOU_APPID}&secret=${YOU_SECRET}').then((result)=>{
        // 提取出来的 access_token
        let access_token = result.data.access_token;
        reqData = {
            'type':'image',
            'offset':0,
            'count':5
        }
        // 再请求获取图片类型的资源
        axios.post(`https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=${access_token}`,reqData).then(result1=>{
            let data = result1.data;
            // 图片资源存放在data.item 中 ,查看结果
            console.log(data.item, data.item[0]);
            res.send();
        })
        
    })
}

        Output content (media_id is what we need to use):

Reference: WeChat Open Documentation

4-4) Return a picture to the user as a reply

const axios = require('axios');

app.post('/wechat-test',(req, res)=>{
    let data = '';
    req.on('data',(chunk)=>{
        data += chunk;
    }).on('end',()=>{
        toJson(data, (err, result)=>{
            let xmlObj = result.xml;
            if(err){
                console.log('错误:',new Date().toLocaleString());
                res.send()
            }else{
                switch (xmlObj.MsgType) {
                    case 'image':
                        dealImgMsg(xmlObj, res);
                        break;
                    case 'text':
                        dealTextMsg(xmlObj, res);
                        break;
                    default:
                        res.send()
                }
            }
            
        })
    })
})

// 处理图片类型的消息-返回固定的图片
function dealImgMsg(xmlObj, res){
    axios.get(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${YOU_APPID}&secret=${YOU_SECRET}`).then((result)=>{
        let access_token = result.data.access_token;
        reqData = {
            'type':'image',
            'offset':0,
            'count':5
        }
        axios.post(`https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=${access_token}`,reqData).then(result1=>{
            let data = result1.data;
            let resXML = `
                <xml>
                    <ToUserName><![CDATA[${xmlObj.FromUserName}]]></ToUserName>
                    <FromUserName><![CDATA[${xmlObj.ToUserName}]]></FromUserName>
                    <CreateTime>${new Date().getTime()}</CreateTime>
                    <MsgType><![CDATA[image]]></MsgType>
                    <Image>
                        <MediaId><![CDATA[${data.item[0].media_id}]]></MediaId>
                    </Image>
                </xml>
            `
            res.send(resXML);
        })
    })
}
// 处理文字类型的消息-返回用户发送来的消息加自定义的内容
function dealTextMsg(xmlObj, res){
    let resXML = `
        <xml>
            <ToUserName><![CDATA[${xmlObj.FromUserName}]]></ToUserName>
            <FromUserName><![CDATA[${xmlObj.ToUserName}]]></FromUserName>
            <CreateTime>${new Date().getTime()}</CreateTime>
            <MsgType><![CDATA[text]]></MsgType>
            <Content><![CDATA[你好, ${xmlObj.Content}]]></Content>
        </xml>
    `;
    res.send(resXML);
}

 

 

Guess you like

Origin blog.csdn.net/hao_13/article/details/130939035