微信開發實踐(二):使用JS-SDK實現自定義分享 Ⅱ

爲了快速幫你們理解,此次的demo就直接修改公衆號官網的示例代碼來給你們演示。若是你們不想聽我囉嗦,能夠直接移步官方文檔——https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115javascript

因爲移動設備調試起來不是特別方便,建議你們先去官網下載微信的web開發者工具,直接在PC上測試。具體用法很是簡單,我就不贅述了。須要注意的是必須得先在此頁面(https://mp.weixin.qq.com/cgi-bin/safecenterstatus?action=devlist&token=711880374&lang=zh_CN)綁定開發者帳號才能使用工具css

上一篇教程我們已經完成JS接口安全域名的綁定,首先,在項目根目錄下建立html靜態頁面html

test.html:java

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>微信JS-SDK Demo</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
    <link rel="stylesheet" href="http://203.195.235.76/jssdk/css/style.css">
    <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
</head>

<body ontouchstart="">
	<div class="wxapi_container">
		<div class="lbox_close wxapi_form">
			<span class="desc">獲取「分享給朋友」按鈕點擊狀態及自定義分享內容接口</span>
			<button class="btn btn_primary" id="onMenuShareAppMessage">onMenuShareAppMessage</button>
		</div>
	</div>
</body>

<script src="//res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script>
    // 微信配置
</script>
<script src="http://203.195.235.76/jssdk/js/zepto.min.js"></script>

</html>

接下來node

① 在須要調用JS接口的頁面引入以下JS文件

http://res.wx.qq.com/open/js/jweixin-1.2.0.js(支持https),若是還不肯定本身網站的協議未來是否支持https,能夠直接省略協議(//res.wx.qq.com/open/js/jweixin-1.2.0.js)jquery

② 後端實現簽名生成算法

下面這段引用自官網:git

全部須要使用JS-SDK的頁面必須先注入配置信息,不然將沒法調用(同一個url僅需調用一次,對於變化url的SPA的web app可在每次url變化時進行調用,目前Android微信客戶端不支持pushState的H5新特性,因此使用pushState來實現web app的頁面會致使簽名失敗,此問題會在Android6.2中修復)。web

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

因爲這些權限驗證配置都是後端生成的,因此咱們須要修改express.js文件,官網提供了生成簽名的算法:ajax

附錄1-JS-SDK使用權限簽名算法

jsapi_ticket
生成簽名以前必須先了解一下jsapi_ticket,jsapi_ticket是公衆號用於調用微信JS接口的臨時票據。正常狀況下,jsapi_ticket的有效期爲7200秒,經過access_token來獲取。因爲獲取jsapi_ticket的api調用次數很是有限,頻繁刷新jsapi_ticket會致使api調用受限,影響自身業務,開發者必須在本身的服務全局緩存jsapi_ticket 算法

  1. 參考如下文檔獲取access_token(有效期7200秒,開發者必須在本身的服務全局緩存access_token):../15/54ce45d8d30b6bf6758f68d2e95bc627.html
  2. 用第一步拿到的access_token 採用http GET方式請求得到jsapi_ticket(有效期7200秒,開發者必須在本身的服務全局緩存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi

成功返回以下JSON:

{
    "errcode": 0,
    "errmsg": "ok",
    "ticket": "bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
    "expires_in": 7200
}

得到jsapi_ticket以後,就能夠生成JS-SDK權限驗證的簽名了。


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

noncestr=Wm3WZYTPz0wzccnW
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg
timestamp=1414587457
url=http://mp.weixin.qq.com?params=value

步驟1. 對全部待簽名參數按照字段名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串string1:

jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW&timestamp=1414587457&url=http://mp.weixin.qq.com?params=value

步驟2. 對string1進行sha1簽名,獲得signature:

0f9de62fce790f9a083d5c99e95740ceb90c27ed

注意事項

  1. 簽名用的noncestr和timestamp必須與wx.config中的nonceStr和timestamp相同。
  2. 簽名用的url必須是調用JS接口頁面的完整URL。
  3. 出於安全考慮,開發者必須在服務器端實現簽名的邏輯

如出現invalid signature 等錯誤詳見附錄5常見錯誤及解決辦法。

ps:咱們這裏使用是全局基礎的access_token,要注意其與關於網頁受權access_token的區別

複述了一堆,下面咱們用node來實現上面的算法,由於是基礎入門教程,我對代碼作了些精簡:

① 後端代碼中不會使用數據庫,經過直接將獲取的access_token寫到文件系統的方式做爲緩存機制(固然你也能夠把access_token放到一個全局變量裏,不過一旦服務器重啓變量就從內存裏消失了,對於測試來講我們仍是珍惜一下有限的調用次數吧)。

② 後端代碼不會涉及定時任務,因爲access_token和jsapi_ticket的時效時間都是2小時,若是生成簽名失敗,須要手動清空緩存(這裏是指刪除文件),由於我會每次生成簽名前會從緩存(這裏是指文件系統)中取accessToken,若是不存在就會從微信服務器從新獲取並刷新緩存(從新生成文件)

修改後的express.js:

const fs = require('fs')
const http = require('http')
const https = require('https')
const crypto = require('crypto')
const express = require('express')
const bodyParser = require('body-parser')
const app = express()

const APP_ID = '測試號的APP_ID'
const APP_SECRET = '測試號的APP_SECRET'
const TOKEN = '手動填寫的TOKEN'

/**
 * 加密/校驗流程以下:
 * 1)將token、timestamp、nonce三個參數進行字典序排序
 * 2)將三個參數字符串拼接成一個字符串進行sha1加密
 * 3)開發者得到加密後的字符串可與signature對比,標識該請求來源於微信
 */
app.get('/verify', function (req, res) {
  const timestamp = req.query.timestamp
  const nonce = req.query.nonce
  const shasum = crypto.createHash('sha1')
  shasum.update([TOKEN, timestamp, nonce].sort().join(''))
  const generatedSignature = shasum.digest('hex')
  if (generatedSignature === req.query.signature) {  // 來自微信服務器
    res.send(req.query.echostr)
  } else {
    res.send('fail')
  }
})

/**
 * 訪問根目錄下的靜態文件
 */
app.get('/file/:path', function (req, res, next) {
  res.sendFile(req.params.path, { root: __dirname })
})

/**
 * 獲取緩存中的accessToken
 * @returns {Promise}
 */
function getAccessToken() {
  if (fs.existsSync('./access-token.txt')) {  // 從文件系統中獲取
    const accessToken = fs.readFileSync('./access-token.txt', { flag: 'r', encoding: 'utf8' })
    console.log('get cached accessToken: ', accessToken)
    return Promise.resolve(accessToken)
  } else {  // 從微信服務器中獲取
    return new Promise((resolve, reject) => {
      https.get('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' + APP_ID + '&secret=' + APP_SECRET, function (req, res) {
        let jsonStr = ''
        req.on('data', function (data) {
          jsonStr += data
        })
        req.on('end', function () {
          const accessToken = JSON.parse(jsonStr)['access_token']
          console.log('refresh access token: ', accessToken)
          fs.writeFileSync('./access-token.txt', accessToken, { flag: 'w', encoding: 'utf8' })
          resolve(accessToken)
        })
      })
    })
  }
}

/**
 * 獲取緩存中的ticket
 * @param {boolean} accessToken
 * @returns {Promise}
 */
function getTicket(accessToken) {
  if (fs.existsSync('./ticket.txt')) {  // 從文件系統中獲取
    const ticket = fs.readFileSync('./ticket.txt', { flag: 'r', encoding: 'utf8' })
    console.log('get cached ticket: ', ticket)
    return Promise.resolve(ticket)
  } else {  // 從微信服務器中獲取
    return new Promise((resolve, reject) => {
      https.get('https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' + accessToken + '&type=jsapi', function (req, res) {
        let jsonStr = ''
        req.on('data', function (data) {
          jsonStr += data
        })
        req.on('end', function () {
          const ticket = JSON.parse(jsonStr)['ticket']
          console.log('refresh ticket: ', ticket)
          fs.writeFileSync('./ticket.txt', ticket, { flag: 'w', encoding: 'utf8' })
          resolve(ticket)
        })
      })
    })
  }
}

/**
 * H5客戶端 獲取分享的信息
 */
app.all('/getWXConfig', function (req, res) {
  const url = req.query.url
  // 獲取jsapi_ticket
  getAccessToken()
    .then(accessToken => getTicket(accessToken))
    .then(ticket => {
      // 生成簽名
      const timestamp = new Date().getTime()  // 時間戳
      const nonceStr = 'niu_bi'  // 隨機字符串

      const tempString = 'jsapi_ticket=' + ticket + '&noncestr=' + nonceStr + '&timestamp=' + timestamp + '&url=' + url
      const shasum = crypto.createHash('sha1')
      shasum.update(tempString)
      const signature = shasum.digest('hex')
      console.log('signature: ', signature)
      res.json({
        appId: APP_ID,
        timestamp: timestamp,
        nonceStr: nonceStr,
        signature: signature
      })
    })
})

// 啓動服務器
const server = app.listen(3000, function () {
  const host = server.address().address
  const port = server.address().port
  console.log('server listening at %s:%s', host, port)
})

③ 經過config接口注入權限驗證配置

修改咱們根目錄下的test.html文件:

<!-- 以上省略 -->
<script>
    // 微信配置

    $.ajax({
        type: 'get',
        url: '/getWXConfig',
        data: {
            url: window.location.href.split('#')[0]
        },
        success: function (config) {
            console.log(config)
            initWechat(config)
        },
        dataType: 'json'
    })

    function initWechat(config) {
        /*
           * 注意:
           * 1. 全部的JS接口只能在公衆號綁定的域名下調用,公衆號開發者須要先登陸微信公衆平臺進入「公衆號設置」的「功能設置」裏填寫「JS接口安全域名」。
           * 2. 若是發如今 Android 不能分享自定義內容,請到官網下載最新的包覆蓋安裝,Android 自定義分享接口需升級至 6.0.2.58 版本及以上。
           * 3. 常見問題及完整 JS-SDK 文檔地址:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html
           *
           * 開發中遇到問題詳見文檔「附錄5-常見錯誤及解決辦法」解決,如仍未能解決可經過如下渠道反饋:
           * 郵箱地址:weixin-open@qq.com
           * 郵件主題:【微信JS-SDK反饋】具體問題
           * 郵件內容說明:用簡明的語言描述問題所在,並交代清楚遇到該問題的場景,可附上截屏圖片,微信團隊會盡快處理你的反饋。
           */
        wx.config({
            debug: true,
            appId: config.appId,
            timestamp: config.timestamp,
            nonceStr: config.nonceStr,
            signature: config.signature,
            jsApiList: [
                'checkJsApi',
                'onMenuShareTimeline',
                'onMenuShareAppMessage',
                'onMenuShareQQ',
                'onMenuShareWeibo',
                'onMenuShareQZone'
            ]
        });
    }

</script>
<script src="http://203.195.235.76/jssdk/js/zepto.min.js"></script>
</html>

在微信web開發者中打開地址http://26s4ag.natappfree.cc/file/test.html(該域名上一個教程經過NATAPP獲取的),若是彈出 { errMsg: config:ok },說明微信配置成功了。

④ 註冊分享狀態回調事件

經過ready接口處理成功驗證

wx.ready(function () {
    /**
     * config信息驗證後會執行ready方法,全部接口調用都必須在config接口得到結果以後,
     * config是一個客戶端的異步操做,因此若是須要在頁面加載時就調用相關接口,則須把
     * 相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則能夠
     * 直接調用,不須要放在ready函數中。
     */
});

經過error接口處理失敗驗證

wx.error(function (res) {
    /**
     * config信息驗證失敗會執行error函數,如簽名過時致使驗證失敗,具體錯誤
     * 信息能夠打開config的debug模式查看,也能夠在返回的res參數中查看,對
     * 於SPA能夠在這裏更新簽名。
     */
});

接口調用說明
全部接口經過wx對象(也可以使用jWeixin對象)來調用,參數是一個對象,除了每一個接口自己須要傳的參數以外,還有如下通用參數:
1.success:接口調用成功時執行的回調函數。
2.fail:接口調用失敗時執行的回調函數。
3.complete:接口調用完成時執行的回調函數,不管成功或失敗都會執行。
4.cancel:用戶點擊取消時的回調函數,僅部分有用戶取消操做的api纔會用到。
5.trigger: 監聽Menu中的按鈕點擊時觸發的方法,該方法僅支持Menu中的相關接口。
(PS:不要嘗試在trigger中使用ajax異步請求修改本次分享的內容,由於客戶端分享操做是一個同步操做,這時候使用ajax的回包會尚未返回)
以上幾個函數都帶有一個參數,類型爲對象,其中除了每一個接口自己返回的數據以外,還有一個通用屬性errMsg,其值格式以下:
調用成功時:"xxx:ok" ,其中xxx爲調用的接口名
用戶取消時:"xxx:cancel",其中xxx爲調用的接口名
調用失敗時:其值爲具體錯誤信息

我這裏只演示一個接口的使用,繼續修改我們的test.html中的 initWechat 函數:

function initWechat(config) {
    /*
        * 注意:
        * 1. 全部的JS接口只能在公衆號綁定的域名下調用,公衆號開發者須要先登陸微信公衆平臺進入「公衆號設置」的「功能設置」裏填寫「JS接口安全域名」。
        * 2. 若是發如今 Android 不能分享自定義內容,請到官網下載最新的包覆蓋安裝,Android 自定義分享接口需升級至 6.0.2.58 版本及以上。
        * 3. 常見問題及完整 JS-SDK 文檔地址:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html
        *
        * 開發中遇到問題詳見文檔「附錄5-常見錯誤及解決辦法」解決,如仍未能解決可經過如下渠道反饋:
        * 郵箱地址:weixin-open@qq.com
        * 郵件主題:【微信JS-SDK反饋】具體問題
        * 郵件內容說明:用簡明的語言描述問題所在,並交代清楚遇到該問題的場景,可附上截屏圖片,微信團隊會盡快處理你的反饋。
        */
    wx.config({
        debug: true,
        appId: config.appId,
        timestamp: config.timestamp,
        nonceStr: config.nonceStr,
        signature: config.signature,
        jsApiList: [
            'checkJsApi',
            'onMenuShareTimeline',
            'onMenuShareAppMessage',
            'onMenuShareQQ',
            'onMenuShareWeibo',
            'onMenuShareQZone'
        ]
    })

    wx.ready(function () {

        // 監聽「分享給朋友」,按鈕點擊、自定義分享內容及分享結果接口
        document.querySelector('#onMenuShareAppMessage').onclick = function () {
            wx.onMenuShareAppMessage({
                title: '我平生最佩服四種人',  // 分享標題
                desc: '看黃片不快進的人,看黃片不打飛機的人,看完黃片還沒打出來的人,打完了還能把黃片看完的人。 由於這表明了人類最珍貴的四種品質:踏實,正直,堅持,執着',  // 分享描述
                link: window.location.origin + '/file/hentai.html',  // 分享連接,該連接域名或路徑必須與當前頁面對應的公衆號JS安全域名一致
                imgUrl: window.location.origin + '/file/huaji.png',  // 分享圖標
                type: 'link',  // 分享類型,music、video或link,不填默認爲link
                dataUrl: '',  // 若是type是music或video,則要提供數據連接,默認爲空
                trigger: function (res) {
                    // 不要嘗試在trigger中使用ajax異步請求修改本次分享的內容,由於客戶端分享操做是一個同步操做,這時候使用ajax的回包會尚未返回
                    alert('用戶點擊發送給朋友')
                },
                success: function (res) {
                    // 用戶確認分享後執行的回調函數
                    alert('已分享')
                },
                cancel: function (res) {
                    // 用戶取消分享後執行的回調函數
                    alert('已取消')
                },
                fail: function (res) {
                    // 分享失敗時執行的回調函數
                    alert(JSON.stringify(res))
                }
            })

            alert('已註冊獲取「發送給朋友」狀態事件')
        }
    })

    wx.error(function (res) {
        alert(res.errMsg)
    })
}

⑤ 測試分享接口是否成功

首先點擊右上角,選擇「發送給朋友」,而後再點擊頁面上的「onMenuShareAppMessage」按鈕,再次分享給朋友,若是兩次的內容不一樣,那麼恭喜你,微信自定義分享功能已經實現啦!

這裏只介紹了「發送給朋友」的功能,其餘同理,更多的接口請參閱微信JS-SDK說明文檔

完整代碼戳這裏

 

下一篇來說解一下微信如何進行頁面受權以及獲取用戶的openId。

相關文章
相關標籤/搜索