preface

Why do we need to understand the development process of wechat public number? Now when writing business in the company, we have more or less come into contact with the development of H5 applications such as wechat official account, enterprise wechat and Dingding, but we usually follow the following three steps in the development:

  • Import JS files. Import JS-SDK files on pages that need to invoke the JS interface
  • Verify the configuration through the config interface injection permission
  • In the process of connecting with wechat, we rarely pay attention to the work done by the server. As a result, it is always unclear when we talk about the official account. Today, we will use NodeJS to completely connect the front and back end of the official account of wechat. Thus familiar with the overall process of wechat public number development

To prepare

**1. Wechat official Account: No. * * the public can in WeChat public platform to apply for, the development use ordinary subscription, WeChat public, divided into normal subscription Numbers, certification subscription Numbers, normal service number, earnest service number, the difference is the number of public types have different privileges different interfaces, such as WeChat payment must be verified service number can be used, For other permissions, you can view the official permissions document

**2. Server: * * as a result of our server and WeChat server to interact, the server must be necessary, you can buy ali or tencent server, if you don’t want to spend money also began to use some network through tools, local IP exposed to the public, commonly used have Ngrok, Frp, etc., the use of specific can click on their own understanding, But most of this tool is not particularly stable, or recommend you to buy a server is more convenient, maybe after the public operation is better with a certain number of fans, these are small 🙈

3 domain name: domain name is not specific, we can find some domain name manufacturers to buy, it is worth noting that after the domain name can be put on record as soon as possible, because sometimes the domain name is the need to put on record

Access to the

Once we’re done with the prep work, what’s the next step

**1. Enter the server configuration. ** After logging in to the official website of wechat public platform, check the protocol to become a developer on the development – Basic Settings page of the official website of wechat public platform, click the “Modify Configuration” button, and fill in the server address (URL), Token and EncodingAESKey, where THE URL is the interface URL that the developer uses to receive wechat messages and events. The Token can be filled in at will by the developer and used to generate the signature (the Token is compared to the Token contained in the interface URL to verify security). EncodingAESKey is manually filled in or randomly generated by the developer and used as the message body encryption and decryption key.

  • The token, TIMESTAMP, and nonce parameters are sorted lexicographically
  • The three parameter strings are concatenated into one string for SHA1 encryption
  • Compare the encrypted string with signature, if the same, identify that the request is from wechat, we return the echostr parameter as is, then the access verification is successful, the complete access code is as follows:
const Koa = require('koa')
const crypto = require('crypto')
const config = require('./config/wx')
const app = new Koa()

app.use(async (ctx, next) => {
    const method = ctx.method
    const signature = ctx.query.signature
    const timestamp = ctx.query.timestamp
    const nonce = ctx.query.nonce
    const echostr = ctx.query.echostr
    // The token is the token we configured
    const token = config.token
    // Sort the token, timestamp, and nonce parameters lexicographically
    const str = [token, timestamp, nonce].sort().join(' ')
    // Concatenate the three parameter strings into one string for sha1 encryption
    const shaStr = crypto.createHash('sha1').update(str).digest('hex')
  if (method === 'GET') {
    // Compare the encrypted string with signature. If they are the same, identify the request as originating from wechat, and return the echostr parameter as is
    if (shaStr === signature) {
      ctx.body = echostr
    } else {
      ctx.body = 'Server authentication failed'
    }
  }
})
app.listen(7000)
Copy the code

After the code is written, we click Submit. If the access is successful, the following prompt will appear:

Pay attention to auto reply

How do we receive this information when the user is paying attention? The answer is that wechat will push this event to us, so that we can send a welcome reply to the user. However, there is a point that needs to be noted that the data returned by wechat to us are data packets in XML format, so how should we deal with it? It can be roughly divided into the following steps:

  1. Handle the control logic of POST type, take out the original data, and accept the XML data packets pushed to us by wechat
  2. Parses the packet, parsing the XML into JSON
  3. Wrap the message we want to reply to in XML format
  4. (If the wechat server does not receive a response within five seconds, it will break the link and resend the request, retry three times in total)
const Koa = require('koa')
const crypto = require('crypto')
const getRawBody = require('raw-body')
const xml2js = require('xml2js')
const config = require('./config/wx')

const app = new Koa()

const parseXml = xml= > {
  return new Promise((resolve, reject) = > {
    xml2js.parseString(xml, { trim: true }, (err, data) => {
      if (err) {
        return reject(err)
      }
      resolve(data)
    })
  })
}

// Convert xml2JS parsed objects into directly accessible objects
const formatMessage = result= > {
  const message = {}
  if (typeof result === 'object') {
    for (let key in result) {
      if (!Array.isArray(result[key]) || ! result[key].length) {continue
      }
      if (result[key].length === 1) {
        const val = result[key][0]
        if (typeof val === 'object') {
          message[key] = formatMessage(val)
        } else {
          message[key] = (val || ' ').trim()
        }
      } else {
        message[key] = result[key].map(item= > formatMessage(item))
      }
    }
  }
  return message
}

app.use(async ctx => {
  const method = ctx.method
  const signature = ctx.query.signature
  const timestamp = ctx.query.timestamp
  const nonce = ctx.query.nonce
  const echostr = ctx.query.echostr

  const token = config.token
  const str = [token, timestamp, nonce].sort().join(' ')
  const shaStr = crypto.createHash('sha1').update(str).digest('hex')

  if (method === 'GET') {
    if (shaStr === signature) {
      ctx.body = echostr
    } else {
      ctx.body = 'Server authentication failed'}}else if (method === 'POST') {

    // Check whether the signature value is valid
    if(shaStr ! == signature) { ctx.body ='Server authentication failed'
    }

    // fetch the raw data
    const xml = await getRawBody(ctx.req, {
      length: ctx.request.length,
      limit: '1mb'.encoding: ctx.request.charset || 'utf-8'
    })

    / / to parse the XML
    const result = await parseXml(xml)

    // Parse XML to JSON
    const message = formatMessage(result.xml) 
    
    // Follow the reply message
    if (message.MsgType === 'event') {
      if (message.Event === 'subscribe') {
        ctx.body = ` <xml> <ToUserName><! [CDATA[${message.FromUserName}]]></ToUserName> <FromUserName><! [CDATA[${message.ToUserName}]]></FromUserName>
            <CreateTime>The ${new Date().getTime()}</CreateTime> <MsgType><! [CDATA[text]]></MsgType> <Content><! [CDATA[Hello, welcome to rich countries!]]></Content> </ XML > '
      }
    }
  }
})

app.listen(7000, () = > {console.log('Server runing at 7000')})Copy the code

Through the above code, we have completed the function of following the automatic reply, and then we can test it. Under normal circumstances, wechat will return the following information configured by us:

Call the wechat interface

Before calling wechat interface, an important point is that all interfaces calling wechat need an Access_token. The access_token is the global unique interface calling credential of the public account. The public account needs to use the Access_token when calling each interface. Duplicate access_token will invalidate the access_token obtained last time.

const redis = require('redis')
const promise = require('bluebird')
const request = promise.promisify(require('request'))

const redisConfig = {
  host: 'localhost'.port: 6379
}
const key = 'access_token'

const redisClient = redis.createClient(redisConfig)
const getAsync = key= > {
  return new Promise((resolve, reject) = > {
    redisClient.get(key, (err, data) => {
      if (err) {
        reject(err)
      }
      resolve(data)
    })
  })
}
/ / to get
const getAccessToken = async() = > {const accessToken = await getAsync(key)
  if(! accessToken) {return updateAccessToken()
  }
  return accessToken
}
/ / update
const updateAccessToken = async() = > {const { appId, appSecret } = config
  const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${appSecret}`
  const { body } = await request({ url: url, json: true })
  const { access_token: accessToken } = body
  await redisClient.set(key, accessToken, 'EX'.7000)
  return accessToken
}

module.exports = getAccessToken
Copy the code

What we do here is to obtain the access_token through the access_token interface provided by wechat. There are three parameters, one is grant_type, which is a fixed value client_credential. AppId and appSecret are respectively appId and Secret configured on wechat public platform. After you get them, you can save them in Redis (of course, it is ok to save them in any place. Here we will simulate the real scene and directly save them in Redis). Set an expiration time (the access_token in the official document is 7200 seconds, here we will get the access_token when setting 7000). If the access_token does not expire next time, we will directly fetch it from Redis. If it expires, we will get it again.

Get user list

Above we have finished obtaining accesstoken, at this time we can call any interface of wechat, take a look at the official interface document for obtaining user list

app.use(async (ctx, next) => {
  try {
    const accessToken = await accessToken()
    const url = `https://api.weixin.qq.com/cgi-bin/user/get?access_token=${accessToken}`
    const { body: users } = await request({ url, json: true })

    console.log('users: ', users);
  } catch (error) {
    console.log('Failed to get user list')}await next()
})
Copy the code

This code is very simple, I believe we can see that the way to call other wechat interfaces are more or less the same, here is not an example.

Webpage authorization front-end JS-SDK call

The most important step of jS-SDK call is to generate signature, but before generating signature, we must know jsapi_ticket, which is a temporary ticket used by public account to invoke wechat JS interface. Normally, a jsapi_ticket has a validity period of 7200 seconds and can be obtained through an access_token. Ticket is very similar to Accesstoken and has the same validity period, so we can directly obtain the ticket by obtaining accesstoken. Instead of the actual code, let’s focus on generating a signature (assuming we’ve got a ticket) and look at the following code:

router.get('/signature', async (ctx, next) => { const jsApiTicket = await getJsApiTicket() const nonceStr = Math.random().toString(36).substr(2, 15) const timestamp = '${parseInt(new Date().gettime () / 1000)}' const url = ctx.request.url // ASCII for all parameters to be signed according to field names After sorting the codes from smallest to largest (lexicographical), use the format of URL key-value pairs (i.e. Key1 =value1&key2=value2...) Concatenated to the string string1: Const STR = 'jsapi_ticket=${jsApiTicket}&nonceStr=${nonceStr}&timestamp=${timestamp}&url=${url}' Get the signature:  const signature = crypto.createHash('sha1').update(str).digest('hex') ctx.body = { signature, timestamp, nonceStr, appId, } })Copy the code

The rules for signature generation are as follows: the fields participating in the signature include noncestr (random string), valid jsapi_ticket, timestamp (timestamp), and url (the url of the current web page, excluding # and the following part). After sorting all parameters to be signed in alphabetical order according to the ASCII code of the field name, use the FORMAT of URL key-value pairs (key1= Value1 &key2=value2…). Concatenated to the string string1. Note that all parameter names are lowercase characters. Sha1 encryption of string1, using the original field name and field value without URL escape, generates the signature and returns it to the front end, and then calls the front end, which is the three steps we started with

axios.get('/signture').then(res= > {
  wx.config({
    debug: true.timestamp: res.timestamp,
    signature: res.signature,
    nonceStr: res.nonceStr,
    appId: res.appId,
    jsApiList: ['chooseImage'],
  })

  wx.ready((a)= > {
    wx.chooseImage({
      // do something
    })
  })
}).catch(error= > {
  console.log(error.message || 'Error obtaining signature')})Copy the code

conclusion

Today, we explained the basic common functions of wechat open platform by configuring access to the last front end. I believe we have some understanding of the development of wechat. If you are developing wechat public number, I hope to help you.