Wechat Public Number Development + node.js Server Deployment

Posted by vmarellano on Fri, 10 May 2019 09:02:02 +0200

Application for Wechat Public Account; Official website

2. Developing documents; Website

III. Application for Interface Test Number and Test Connection

url: To be accessed by the external network, the local server can build ngrok. Of course, if you have money, you can buy one; ngrok runs differently every time. Instruction ngrok http port number. Intranet penetration, sometimes timeliness.
token:Parameters for WeChat message encryption
Test whether to connect and what parameters are there

Configuring local servers
index.js

// Introducing express module
const express = require('express')
// app application object
const app = express();

app.use((req,res,next) => {
    // Which is the Wechat Server Auto Developer Server?
        //Test number
        console.log(req.query)
})

//port number
app.listen(4000,()=>console.log("Server enablement"))

Make sure the local server opens and click on the submit button, where the Wechat Public Number has just been configured

// Signature:'35eab388c24c9d0deafa3bd12513d6f0cac7ac03', / / / Wechat Encrypted Signature
 // echostr:'4361760528703928317', // Weixin random string
 // timestamp:'1555216285', / / / the time of the occurrence of wechat
 // Nonce:'316998338'}//Random Number

4. Calculate whether the parameters of signature encrypted signature are the same as those transmitted by wechat. Similarly, it means the message from the Wechat server.

a. The three parameters (timestamp, nonce, token) involved in the encrypting of Wechat are grouped together, and dictionaries are arranged and grouped together to form an array.
b. Splice all the parameters of the array into a string and encrypt it with sha1.
c. If the same, the message comes from the Wechat server and returns echostr to the Wechat server; if not, an error will be reported.

index.js

// Introducing express module
const express = require('express')
// app application object
const app = express();
const  sha1= require('sha1')

const config = {
    token:'sd*********dssda22345',
    appID:'wxf*******0cf5a8b',
    appsecret:'9d1fa6c8eee80c5**********cf8b53'
}

app.use((req,res,next) => {
       // console.log(req.query)
        // Signature:'35eab388c24c9d0deafa3bd12513d6f0cac7ac03', //Wechat Encrypted Signature
        //   Echostr:'4361760528703928317', // Weixin random string
        //   Timestamp:'1555216285', / / / the time of the occurrence of wechat
        //   Nonce:'316998338'}//Random Number
    const {signature,echostr,timestamp,nonce}  = req.query;   
    const {token} =config;
    const arr = [timestamp , nonce , token]
    const arrSort = arr.sort();
    console.log(arrSort)
    const str = arrSort.join('')
    const sha1Str = sha1(str)
    console.log(sha1Str)
    if(sha1Str === signature){
        res.send(echostr)
    }else{
        res.send('error')
    }

})

//port number
app.listen(4000,()=>console.log("Server enablement"))

Wechat Public Number Prompt Configuration Successful

V. Get access_token

Access_token is the globally unique interface invocation credential of public number, and it is necessary to use access_token when public number invokes each interface. Developers need to be properly saved. Access_token should be stored in at least 512 character spaces. Access_token is currently valid for 2 hours and needs to be refreshed regularly. Repeated access_token will invalidate the last access_token retrieved, and the number of requests.

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

Design ideas:
1. The first time there is no local, send a request to get access_token, save (local)
2. The second time, read locally to determine whether it is overdue. No direct use; if expired, re-request. Judgment of expiration time. Current time + 7200, before

//Get the unique credentials for access_token Wechat calling interface
// Required url parameters
const {appID , appsecret} = require('../config')
const rp = require('request-promise-native')
const {readFile , writeFile} = require('fs')
class Wechat {
    constructor(){

    }
    //Get access_token
    getAccessToken(){
        const url =`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appID}&secret=${appsecret}`;
        // Send request
        // The server side cannot use request request-promise-native for ajax, and the promise object is returned.
        return new Promise((resolve,reject) => {
            rp({method:'get',url,json:true})
            .then(res =>{
                console.log(res)
                res.expires_in = Date() + (res.expires_in -300)*1000;
                resolve(res)
                // { access_token:
                //     '20_ZCrQnrFmz20x21BwfYy9b2fvJvqQPvQZ5NQMeiy5LZwRfKXgtgArpNFl-WnGDT0QvvTI94CjJ459Fy8IfFn4g00tskZ_Z76xgEDe8fXYnGXd9PwkZhQJ0_mN8FE_sVPVPz1x7OFy0RRPbyfMVXGaAJAIKT',
                //    expires_in: 7200 }
            })
            .catch(err =>{
                console.log(err);
                reject('getAccessToken errer')
            })
        })
    }
    //Save access_token
    /**
     * @param accessToken
     */
    saveAccessToken(accessToken){
        //Converting objects to json strings
        accessToken = JSON.stringify(accessToken);

        return new Promise((resolve,reject)=>{
            writeFile('./accessToken.txt',accessToken,err => {
                if(!err){
                    console.log("file write success");
                    resolve();
                }else{
                    console.log(err);
                    reject();
                }
            })
        })
    }
    // Read access_token
    readAccessToken(){
        return new Promise((resolve,reject)=>{
            readFile('./accessToken.txt',(err,data) => {
                if(!err){
                    console.log("file read success");
                    data = JSON.parse(data)
                    resolve(data);
                }else{
                    console.log(err);
                    reject();
                }
            })
        })
    }
    // Is it effective?
    isValidAccessToken(data){
        if(!data&&data.access_token&&!data.expires_in){
            return false;
        }
        return data.expires_in > data.now()
    }
    // Get access_token without expiration
    /**
     * @param
     */
    fetchAccessToken(){
        // console.log(this.access_token)
        if(this.access_token && this.expires_in && this.isValidAccessToken(this)) {
            // Save access_token
            return Promise.resolve({
                access_token:this.access_token,
                expires_in:this.expires_in,
            })
        }
        return this.readAccessToken()
            .then( async res => {
                // Local Documents
                if(this.isValidAccessToken(res)){
                    resolve(res)
                }else{
                    // Be overdue
                const res= await this.getAccessToken();
                await this.saveAccessToken(res);
                return Promise.resolve(res)
                }
            })
            .catch( async err => {
                const res= await this.getAccessToken();
                await this.saveAccessToken(res);
                return Promise.resolve(res)
            })
            .then(res => {
                this.access_token = res.access_token;
                this.expires_in = res.expires_in;
                return Promise.resolve(res);
            })        
    }
}
// To test whether the file successfully creates a new class, call fetchAccessToken() method

6. Receiving and reusing user information

Data information flow Data xml format text
Automatic recovery
Attention points
Automatically reply within 1.5 seconds
2. Developer Data Format

until.js

const { parseString } = require('xml2js')
module.exports = {
    // Streaming Information Mosaic
    getUserDataAsync(req) {
        return new Promise((resolve, reject) => {
            let xmlData = '';
            req.on('data', data => {
                console.log(data)
                xmlData += data.toString();
            })
                .on('end', () => {
                    resolve(xmlData)
                })
        })
    },
    //xml to js
    parseXMLAsync(xmlData){
        return new Promise((resolve,reject)=>{
            parseString(xmlData,{trim:true},(err,data)=>{
                if(!err){
                    resolve(data)       
                }else{
                    reject("parseXMLAsync errer:" +err)
                }
            })
        })
    },
    // Fields change arrays to corresponding values
    formatMessage(jsData){
        let message = {};
        jsData = jsData.xml;
        //Determine whether the data is an object?
        if(typeof jsData === "object"){ 
            for(let key in jsData){
                let value = jsData[key];
                // Filter empty data
                if(Array.isArray(value)&&value.length>0){
                    message[key] = value[0];
                }
            }            
        }
        return message;
    }
}

auth.js

// Like middleware, encapsulation
const config = require('../config')
const sha1 = require('sha1')
const { getUserDataAsync , parseXMLAsync ,formatMessage} = require('../utils/tool.js')
module.exports = () => {
    return async (req, res, next) => {
        // console.log(req.query)
        // Signature:'35eab388**************************** 6f0cac7ac03', //Wechat Encrypted Signature
        //   Echostr:'436176052*******8317', //Weixin random string
        //   Timestamp:'155****** 16285', // the time of the occurrence of wechat
        //   Nonce:'316998338'}//Random Number
        const { signature, echostr, timestamp, nonce } = req.query;
        const { token } = config;
        const arr = [timestamp, nonce, token]
        const arrSort = arr.sort();
        const str = arrSort.join('')
        const sha1Str = sha1(str)
        // const sha1Str =  sha1([timestamp,nonce,token].sort().join(''))
        // console.log(sha1Str)


        /**
         * Wechat Server Sends Two Types of Developers Getpost to Server
         */

        // { signature: 'd25a9573617f**********7e87d4062475a',
        // timestamp: '1558****176',
        // nonce: '8018***15',
        // openid: 'o*************w3nObg' }
        if (req.method === "GET") {
            if (sha1Str === signature) {
                res.send(echostr)
            } else {
                res.send('wechat server error')
            }
        } else if (req.method === "POST") {
            if (sha1Str !== signature) {
                res.send("errer")
            } else {
                console.log(req.query)
            }
            const xmlData = await getUserDataAsync(req);
            // buffer data
            console.log(xmlData)


                // < xml > 
                //     <ToUserName> <![CDATA [gh_d772f38c5ba2]> </ToUserName>//developer id]
                //     <FromUserName> <![CDATA [o1Q3x03Pn0eLpM8wlwFHn7w3nObg]]> </FromUserName>//user openid
                //     <CreateTime>1555231639</CreateTime>//Time of Delivery
                //     <MsgType><![CDATA[text]></MsgType>//information type
                //     <Content><![CDATA[123]></Content>//Information Content
                //     <MsgId>22265565114514992</MsgId>//Message id Wechat Server for 3 days
                // </xml >
                // xml object to js object
            const jsData = await parseXMLAsync (xmlData)
                console.log(jsData)

            /**
             * Format
             */
            var message = formatMessage(jsData)

            // Is user information text information?
            let content = 'What are you talking about? I can't hear you clearly.';
            if(message.MsgType == 'text'){
                if(message.Content === '0'){
                    content = 'Sorry, I'm a policeman.';
                }else if (message.Content === '1'){
                    content = 'No previous choice';
                }else if (message.Content === '2'){
                    content = 'Now I want to be a good man.';
                }else if (message.Content.match('Hate')){
                    content = 'It's all right. I'm here.';
                }else if (message.Content.match('like')){
                    content = 'I like you, too.';
                }
            }

            let replyMessage =`<xml>
            <ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
            <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
            <CreateTime>${Date.now()}</CreateTime>
            <MsgType><![CDATA[text]]></MsgType>
            <Content><![CDATA[${content}]]></Content>
          </xml>`
            //Return to the server
            console.log(replyMessage)
            res.send(replyMessage);
            
            //If the request is not answered, it will be three times.
            // res.end('')
        } else {
            res.send("errer")
        }
    }
}

Topics: SHA1 xml JSON network