nodejs微信jssdk後端接口

寫過了兩個微信的頁面,遇到了挺多不會的問題,當時也是本身邊查資料,邊實踐完成了簡單的需求,恰好如今有空,把以前的東西整理一遍。前端

與普通的手機頁面不一樣的是,微信頁面提供給你了調用微信APP內置功能的接口,能夠實現更復雜的功能。git

jssdk的前端使用

  • 前端頁面調用jssdk首先要通綁定「公衆號設置」的「功能設置」裏填寫「JS接口安全域名」github

  • 而後在頁面中引入http://res.wx.qq.com/open/js/...redis

  • 調用 wx.config({...}) 來驗證權限配置算法

  • 而後可根據須要 調用微信所提供的接口 數據庫

後端返回接口

在前端調用時wx.config({...})中須要的參數須要咱們本身進行返回json

wx.config({
    debug: true, // 開啓調試模式,調用的全部api的返回值會在客戶端alert出來,若要查看傳入的參數,能夠在pc端打開,參數信息會經過log打出,僅在pc端時纔會打印。
    appId: '', // 必填,公衆號的惟一標識
    timestamp: , // 必填,生成簽名的時間戳
    nonceStr: '', // 必填,生成簽名的隨機串
    signature: '',// 必填,簽名
    jsApiList: [] // 必填,須要使用的JS接口列表
});

其中 timestamp , nonceStr, signature,是須要後端計算返回的。後端

簽名獲取方法

簽名生成規則以下:參與簽名的字段包括noncestr(隨機字符串), 有效的jsapi_ticket, timestamp(時間戳), url(當前網頁的URL,不包含#及其後面部分) 。對全部待簽名參數按照字段名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串string1。這裏須要注意的是全部參數名均爲小寫字符。對string1做sha1加密,字段名和字段值都採用原始值,不進行URL 轉義。api

而其中的 jsapi_ticket 是經過 access_token 來獲取的,且二者都有過時時間(7200秒)其中 jsapi_ticket 更是限制了獲取次數。
因此爲了保存二者,使用redis數據庫保存在內存中是個很好的選擇(可快速讀取,並設置過時時間)。promise

token獲取方法:

/**
 * 獲取token
 * @return {promise} res 值爲token
 */
function getToken () {
  return redis.getVal('token')  // 首先讀取 redis 是否存在token
    .then(function (res) {  
      if (res === null) {   // 若不存在,則返回savetoken() 獲取
        // console.log('不存在token 調用saveToken')
        return saveToken ()
      } else {              // 若存在 則直接返回
        // console.log('存在token 直接返回')
        return res 
      }
    })
    .catch(function (err) {   // 捕獲 錯誤
      console.log(err)
    })
}

/**
 * 從服務端獲取token 並保存在redis中
 * @return {promise} 值 爲 token
 */
function saveToken () {

  return new Promise((resolve, reject) => {

    let reqUrl = config.gettoken_url // https://api.weixin.qq.com/cgi-bin/token?
    let params = {
      grant_type: 'client_credential',
      appid: config.appid,
      secret: config.appsecret
    }

    let options = {
      method: 'get',
      url: reqUrl + qs.stringify(params)
    }

    request(options, function (err, res, body) {
      if (res) {
        let bodys = JSON.parse(body)
        let expires = bodys.expires_in
        let token = bodys.access_token
        
        redis.setKey('token', token, expires)  // 將token 保存到 redis
          .catch(function (err) {
            console.log(err)
          })

        resolve(token)
      } else {
        reject(err)
      }
    })
  })
}

在配置文件中配置好所須要的appid和appsecret,首先查看redis中是否存在,若是存在就直接返回,沒有的話,就調用saveToken去獲取並保存在redis中

jsapi_ticket 獲取方法

同理,jsapi_ticket 也採用一樣的方式去獲取

/**
 * 獲取ticket
 * @return {promise} res 值爲ticket
 */

function getJsTicket() {  // 獲取token
  return redis.getVal('ticket')  // 首先讀取 redis 是否存在ticket
    .then(function (res) {  
      if (res === null) {   // 若不存在,則返回saveJsTicket() 獲取
        // console.log('不存在ticket 調用saveJsTicket')
        return saveJsTicket ()
      } else {              // 若存在 則直接返回
        // console.log('存在ticket 直接返回')
        return res 
      }
    })
    .catch(function (err) {   // 捕獲 錯誤
      console.log(err)
    })
}

/**
 * 從服務端獲取ticket 並保存在redis中
 * @return {promise} 值 爲 ticket
 */
function saveJsTicket () {

  return new Promise((resolve, reject) => {

    getToken().then(function (token) {

      let reqUrl = config.ticket_start + token + config.ticket_end
      let options = {
        method: 'get',
        url: reqUrl
      }

      request(options, function (err, res, body) {
        if (res) {
          let bodys = JSON.parse(body)     // 解析微信服務器返回的
          let ticket = bodys.ticket        // 獲取 ticket
          let expires = bodys.expires_in   // 獲取過時時間

          redis.setKey('ticket', ticket, expires)  // 將ticket 保存到 redis
            .catch(function (err) {
              console.log(err)
            })
          resolve(ticket)
        } else {
          reject(err)
        }
      })
    }).catch(function (err) {
        console.log(err)
    })
  })
}

簽名算法

在獲取jsapi_ticket後就能夠生成JS-SDK權限驗證的簽名了

/**
 * 1. appId 必填,公衆號的惟一標識
 * 2. timestamp 必填,生成簽名的時間戳
 * 3. nonceStr 必填,生成簽名的隨機串
 * 4. signature 必填,簽名
 */

const crypto = require('crypto')
const getJsTicket = require('./getJsTicket')
const config = require('../../config')  // 微信設置

// sha1加密
function sha1(str) {
  let shasum = crypto.createHash("sha1")
  shasum.update(str)
  str = shasum.digest("hex")
  return str
}

/**
 * 生成簽名的時間戳
 * @return {字符串} 
 */
function createTimestamp () {
  return parseInt(new Date().getTime() / 1000) + ''
}

/**
 * 生成簽名的隨機串
 * @return {字符串} 
 */
function createNonceStr () {
  return Math.random().toString(36).substr(2, 15)
}

/**
 * 對參數對象進行字典排序
 * @param  {對象} args 簽名所需參數對象
 * @return {字符串}    排序後生成字符串
 */
function raw (args) {
  var keys = Object.keys(args)
  keys = keys.sort()
  var newArgs = {}
  keys.forEach(function (key) {
    newArgs[key.toLowerCase()] = args[key]
  })

  var string = ''
  for (var k in newArgs) {
    string += '&' + k + '=' + newArgs[k]
  }
  string = string.substr(1)
  return string
}

/**
* @synopsis 簽名算法 
*
* @param jsapi_ticket 用於簽名的 jsapi_ticket
* @param url 用於簽名的 url ,注意必須動態獲取,不能 hardcode
*
* @returns {對象} 返回微信jssdk所需參數對象
*/
function sign (jsapi_ticket, url) {
  var ret = {
    jsapi_ticket: jsapi_ticket,
    nonceStr: createNonceStr(),
    timestamp: createTimestamp(),
    url: url
  }
  var string = raw(ret)
  ret.signature = sha1(string)
  ret.appId =  config.appid
  return ret
}

/**
 * 返回微信jssdk 所需參數對象
 * @param  {字符串} url 當前訪問URL
 * @return {promise}     返回promise類 val爲對象
 */
function jsSdk (url) {
  return getJsTicket()
    .then(function (ticket) {
      return sign(ticket, url)
    })
    .catch(function (err) {
      console.log(err)
    })
}

function routerSdk (req, res, next) {
  let clientUrl = req.body.url
  if (clientUrl) {
    jsSdk(clientUrl)
      .then(function (obj) {
        res.json(obj)
      })
  } else {
    res.end('no url')
  }
}

module.exports = routerSdk

以上基本就完成了後端返回簽名的過程(省略了redis部分)。具體細節可參考我當時的練手項目中的代碼。
至此,前端就可使用jssdk來完成功能的調用了。
ps:某次使用錄音接口作了一個功能,可是發現,微信服務器只會保存3天數據,須要本身下載到本身的服務器才行,不知道諸位有沒作過相似的需求,給我提供下指導啥的,感激涕零~

後記

後來又寫過一個獲取用戶信息的頁面,感受也是挺經常使用的就寫個demo出來看看吧(沒有作access_token的保存,好像是沒有獲取次數限制)。

router.get('/', function(req, res, next){
  console.log("oauth - login")

  // 第一步:用戶贊成受權,獲取code
  let router = 'get_wx_access_token'
  // 這是編碼後的地址
  let return_uri = encodeURIComponent(base_url + router)
  console.log('回調地址:' + return_uri)
  let scope = 'snsapi_userinfo'

  res.redirect('https://open.weixin.qq.com/connect/oauth2/authorize?appid='+appid+'&redirect_uri='+return_uri+'&response_type=code&scope='+scope+'&state=STATE#wechat_redirect')
})

// 第二步:經過code換取網頁受權access_token
router.get('/get_wx_access_token', function(req,res, next){
  console.log("get_wx_access_token")
  console.log("code_return: "+req.query.code)
  let code = req.query.code
  request.get(
    {   
      url:'https://api.weixin.qq.com/sns/oauth2/access_token?appid=' + appid + '&secret=' + appsecret+'&code=' + code + '&grant_type=authorization_code',
    },
    function(error, response, body){
      if(response.statusCode === 200){

        // 第三步:拉取用戶信息(需scope爲 snsapi_userinfo)
        // console.log(JSON.parse(body))

        let data = JSON.parse(body)
        let access_token = data.access_token
        let openid = data.openid

        request.get(
          {
            url:'https://api.weixin.qq.com/sns/userinfo?access_token='+access_token+'&openid='+openid+'&lang=zh_CN',
          },
          function(error, response, body){
            if(response.statusCode == 200){

              // 第四步:根據獲取的用戶信息進行對應操做
              let userinfo = JSON.parse(body)
              console.log(JSON.parse(body))
              console.log('獲取微信信息成功!')

              小測試,實際應用中,能夠由此建立一個賬戶
              res.send("\
                  <h1>"+userinfo.nickname+" 的我的信息</h1>\
                  <p><img src='"+userinfo.headimgurl+"' /></p>\
                  <p>"+userinfo.city+","+userinfo.province+","+userinfo.country+"</p>\
              ")

            }else{
              console.log(response.statusCode)
            }
          }
        )
      }else{
        console.log(response.statusCode)
      }
    }
  )
})
相關文章
相關標籤/搜索