前端對接微信分享功能徹底指南

  • 蘇格團隊
  • 做者:Dee
  • 交流QQ羣:855833773

背景

最近,因爲公司業務須要,接入了微信web端分享接口。雖然微信的接口文檔已經很詳細了,可是缺乏實戰代碼。小編搜了一下掘金網站好像也不多這方面的分享(或許是太過簡單,大神們都不屑於分享這類經驗。固然也有客觀緣由,如今大多都作小程序了,微信web端的流量被分流了不少。node

準備

此爲微信公衆平臺接口文檔地址web

此爲微信公衆平臺接口測試賬號申請地址算法

此爲獲取基礎access_token地址json

此爲微信JS-SDK說明文檔地址小程序

如下nodejs端是基於egg框架。api

開發

一、申請公衆號或測試版公衆號

公衆號申請(這裏不詳說公衆號申請,有須要本身去官網看文檔)用於生產環境的開發。緩存

對於前期開發階段,能夠去微信公衆平臺接口測試賬號申請,微信掃描二維碼便可申請。安全

注意事項:服務器

一、填寫JS接口安全域名,綁定服務器域名微信

二、掃描「測試號二維碼」,把你的微信號加入列表,而後你的微信就有權限進行測試了。沒有加入列表的微信號是沒有權限使用微信接口的,這裏要注意一下。

至此,測試公衆號配置完成。

二、獲取基礎access_token接口。

微信有兩個access_token,一個是基礎access_token,一個是網頁受權access_token,具體區別,如圖所述:

咱們調用的微信分享接口只須要基礎 access_token就好。

接口api:

(get)api.weixin.qq.com/cgi-bin/tok…

參數:

grant_type:填寫「client_credential」

appid: 公衆號appId

secret: 公衆號secret

返回值:

成功時

{
    "access_token":"ACCESS_TOKEN",
    "expires_in":7200
}
複製代碼

expires_in爲該access_token的有限時間,因爲微信對於獲取accesss_token接口天天有次數限制,因此咱們須要把access_token存到服務器裏,等到其失效後再從新發起請求。

異常時

{
    "errcode":錯誤碼,
    "errmsg":錯誤信息
}
複製代碼

錯誤碼:

  • -1 系統繁忙,此時請開發者稍候再試

  • 0 請求成功

  • 40001 AppSecret錯誤或者AppSecret不屬於這個公衆號,請開發者確認AppSecret的正確性

  • 40002 請確保grant_type字段值爲client_credential

  • 40164 調用接口的IP地址不在白名單中,請在接口IP白名單中進行設置

注意:

一、當遇到錯誤碼爲40164(調用接口的IP地址不在白名單中,請在接口IP白名單中進行設置),要去到公衆號的白名單列表加上本身服務器的IP,測試公衆號帳號是不用配置白名單的,因此在把測試公衆號參數換成正式公衆號參數時,記得配置白名單。

代碼片斷:

const { ctx, config } = this;
    let timestamp = new Date().valueOf();
    // 判斷緩存裏是否有access_token且沒有過時,而且獲取該access_token時的appId與如今的一致,判斷appId是爲了不切換不一樣公衆號配置時沒有清緩存出現錯誤
    // 若是以上條件不能同時知足,則從新請求access_token
    if (!ctx.session.tokenObj || ctx.session.tokenObj.expires_in < timestamp || ctx.session.tokenObj.app_id !== config.wx.appId) {
        const tokenResult = await ctx.curl(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${config.wx.appId}&secret=${config.wx.secret}`, {
            dataType: 'json'
        });
        if (tokenResult.status === 200 && tokenResult.data && tokenResult.data.access_token) {
            ctx.session.tokenObj = {
                access_token: tokenResult.data.access_token,
                expires_in: timestamp + tokenResult.data.expires_in * 1000,
                app_id: config.wx.appId
            };
        } else {
            // 記錄請求錯誤日誌,方便定位錯誤
            // 由於該緩存access_token已經不能使用,請求錯誤時記得把access_token緩存也清空。
            ctx.session.tokenObj = null;
            ctx.logger.error(new Error(`wxconfig: ${JSON.stringify(config.wx)}`));
            ctx.logger.error(new Error(`tokenResult: ${JSON.stringify(tokenResult)}`));
        }
    }
複製代碼

三、獲取jsapi_ticket

接口api:

(get)api.weixin.qq.com/cgi-bin/tic…

參數:

access_token:上一步請求返回的access_token

type:'jsapi'

返回值:

成功時:

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

與access_token同樣,expires_in爲該ticket的有限時間,因爲微信對於獲取ticket接口天天有次數限制,因此咱們須要把access_token存到服務器裏,等到其失效後再從新發起請求。

異常時:

{
"errcode":42001,
"errmsg":"access_token expired hint: [5bugDA09718938!]"
}
複製代碼

代碼:

timestamp = new Date().valueOf();
// 判斷緩存裏是否有jsapi_ticket且沒有過時,而且獲取該jsapi_ticket時的appId與如今的一致,判斷appId是爲了不切換不一樣公衆號配置時沒有清緩存出現錯誤
// 若是以上條件不能同時知足,則從新請求access_token
if (!ctx.session.jsapiObj || ctx.session.jsapiObj.expires_in < timestamp || ctx.session.jsapiObj.app_id !== config.wx.appId) {
    const jsapiResult = await ctx.curl(`https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${ctx.session.tokenObj.access_token}&type=jsapi`, {
        dataType: 'json'
    });
    if (jsapiResult.status === 200 && jsapiResult.data && jsapiResult.data.errcode === 0) {
        ctx.session.jsapiObj = {
            ticket: jsapiResult.data.ticket,
            expires_in: timestamp + jsapiResult.data.expires_in * 1000,
            app_id: config.wx.appId
        };
        ctx.logger.error(new Error(`jsapiResult:success: ${JSON.stringify(jsapiResult)}`));
    } else {
        // 記錄請求錯誤日誌,方便定位錯誤
        // 由於該緩存jsapi_ticket已經不能使用,請求錯誤時記得把jsapi_ticket緩存也清空。
        ctx.session.jsapiObj = null;
        ctx.logger.error(new Error(`wxconfig: ${JSON.stringify(config.wx)}`));
        ctx.logger.error(new Error(`jsapiResult: ${JSON.stringify(jsapiResult)}`));
    }
}
複製代碼

我發現,這個api基本不會怎麼出現錯誤碼,基本上access_token若是沒有問題,這個api的調用也不會報錯。

常出現的錯誤可能是上一步的access_token緩存策略不合理致使這個接口的access_token參數的值不是有效的access_token。

四、獲取簽名算法signature值

簽名生成規則以下:

參與簽名的字段包括

有效的jsapi_ticket、

noncestr(隨機字符串)、

timestamp(時間戳)、

url(當前網頁的URL,不包含#及其後面部分) 。

對全部待簽名參數按照字段名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串string1。這裏須要注意的是全部參數名均爲小寫字符。對string1做sha1加密,字段名和字段值都採用原始值,不進行URL 轉義

代碼:

const jsapi_ticket = ctx.session.jsapiObj.ticket;
const uuidv1 = require('uuid/v1');
const noncestr = uuidv1();
timestamp = new Date().valueOf();
const { url } = ctx.query;
const string1 = `jsapi_ticket=${jsapi_ticket}&noncestr=${noncestr}&timestamp=${timestamp}&url=${url}`;
const crypto = require('crypto');
const hash = crypto.createHash('sha1');
hash.update(string1);
const signature = hash.digest('hex');
複製代碼

注意:

  • 簽名用的noncestr和timestamp必須與下一步的wx.config中的nonceStr和timestamp相同。
  • 簽名用的url必須是調用JS接口頁面的完整URL
  • 出於安全考慮,建議在服務器端實現簽名邏輯。
  • 參數前後順序就jsapi_ticket、noncestr、timestamp、url,這個順序搞錯會致使簽名

簽名算法這一步很關鍵,不合理會致使下一步出現錯誤。具體的錯誤相對應的解決辦法能夠查看官方文檔,裏面很詳細。地址:微信JS-SDK說明文檔,查看該網站的 附錄5-常見錯誤及解決方法


以上步驟都是node端實現,如下爲web端的代碼。

五、引入微信js文件

官方js地址:res.wx.qq.com/open/js/jwe…

官方js備用地址: res2.wx.qq.com/open/js/jwe…

六、經過wx.config接口注入權限驗證配置

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

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

jsApiList具體可查看:微信JS-SDK說明文檔,查看該網站的 附錄2-全部JS接口列表

七、調用微信分享接口

代碼:

wx.ready(function() {
                const title = '分享標題';
                const desc = '分享描述';
                const imgUrl = '分享圖片連接';
                // 朋友圈
                wx.onMenuShareTimeline({
                    title: title, // 分享標題
                    link: url, // 分享連接,該連接域名或路徑必須與當前頁面對應的公衆號JS安全域名一致
                    imgUrl: imgUrl // 分享圖標
                });

                // 微信朋友
                wx.onMenuShareAppMessage({
                    title: title, // 分享標題
                    desc: desc, // 分享描述
                    link: url, // 分享連接,該連接域名或路徑必須與當前頁面對應的公衆號JS安全域名一致
                    imgUrl: imgUrl, // 分享圖標
                    type: 'link', // 分享類型,music、video或link,不填默認爲link
                    dataUrl: '' // 若是type是music或video,則要提供數據連接,默認爲空
                });

                // qq
                wx.onMenuShareQQ({
                    title: title, // 分享標題
                    desc: desc, // 分享描述
                    link: url, // 分享連接
                    imgUrl: imgUrl // 分享圖標
                });

                // qq空間
                wx.onMenuShareQZone({
                    title: title, // 分享標題
                    desc: desc, // 分享描述
                    link: url, // 分享連接
                    imgUrl: imgUrl // 分享圖標
                });

                // 騰訊微博
                wx.onMenuShareWeibo({
                    title: title, // 分享標題
                    desc: desc, // 分享描述
                    link: url, // 分享連接
                    imgUrl: imgUrl // 分享圖標
                });
            });
複製代碼

另外還有一些調用成功success事件、調用失敗fail事件、用戶點擊取消分享cancel事件。具體可看:微信JS-SDK說明文檔 - JSSDK使用步驟 - 接口調用說明

至此,微信分享接口已經可用了。但願對剛接觸微信接口的你有幫助。

所有代碼

// NODE端
    async getWXApiTicket() {
        const { ctx, config } = this;
        let timestamp = new Date().valueOf();
        if (!ctx.session.tokenObj || ctx.session.tokenObj.expires_in < timestamp || ctx.session.tokenObj.app_id !== config.wx.appId) {
            const tokenResult = await ctx.curl(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${config.wx.appId}&secret=${config.wx.secret}`, {
                dataType: 'json'
            });
            if (tokenResult.status === 200 && tokenResult.data && tokenResult.data.access_token) {
                ctx.session.tokenObj = {
                    access_token: tokenResult.data.access_token,
                    expires_in: timestamp + tokenResult.data.expires_in * 1000,
                    app_id: config.wx.appId
                };
            } else {
                ctx.session.tokenObj = null;
                ctx.logger.error(new Error(`wxconfig: ${JSON.stringify(config.wx)}`));
                ctx.logger.error(new Error(`tokenResult: ${JSON.stringify(tokenResult)}`));
            }
        }
        let res = {
            code: 500,
            msg: '獲取失敗'
        };
        if (ctx.session.tokenObj && ctx.session.tokenObj.app_id === config.wx.appId) {
            timestamp = new Date().valueOf();
            if (!ctx.session.jsapiObj || ctx.session.jsapiObj.expires_in < timestamp || ctx.session.jsapiObj.app_id !== config.wx.appId) {
                const jsapiResult = await ctx.curl(`https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${ctx.session.tokenObj.access_token}&type=jsapi`, {
                    dataType: 'json'
                });
                if (jsapiResult.status === 200 && jsapiResult.data && jsapiResult.data.errcode === 0) {
                    ctx.session.jsapiObj = {
                        ticket: jsapiResult.data.ticket,
                        expires_in: timestamp + jsapiResult.data.expires_in * 1000,
                        app_id: config.wx.appId
                    };
                } else {
                    ctx.session.jsapiObj = null;
                    ctx.logger.error(new Error(`wxconfig: ${JSON.stringify(config.wx)}`));
                    ctx.logger.error(new Error(`jsapiResult: ${JSON.stringify(jsapiResult)}`));
                }
            }
            if (ctx.session.jsapiObj && ctx.session.jsapiObj.app_id === config.wx.appId) {
                const jsapi_ticket = ctx.session.jsapiObj.ticket;
                const uuidv1 = require('uuid/v1');
                const noncestr = uuidv1();
                timestamp = new Date().valueOf();
                const { url } = ctx.query;
                const string1 = `jsapi_ticket=${jsapi_ticket}&noncestr=${noncestr}&timestamp=${timestamp}&url=${url}`;
                const crypto = require('crypto');
                const hash = crypto.createHash('sha1');
                hash.update(string1);
                const signature = hash.digest('hex');
                res = {
                    code: 0,
                    data: {
                        nonceStr: noncestr,
                        timestamp,
                        signature,
                        appId: config.wx.appId,
                        jsapi_ticket,
                        string1
                    }
                };
            }
        }
        ctx.body = res;
    }

    // JS端
    const wx = window['wx'];
    const url = location.href.split('#')[0];
    $.get('/getWXApiTicket?url=' + encodeURIComponent(url), function(res) {
        if (res.code === 0) {
            wx.config({
                debug: false, // 開啓調試模式,調用的全部api的返回值會在客戶端alert出來,若要查看傳入的參數,能夠在pc端打開,參數信息會經過log打出,僅在pc端時纔會打印。
                appId: res.data.appId, // 必填,公衆號的惟一標識
                timestamp: res.data.timestamp, // 必填,生成簽名的時間戳
                nonceStr: res.data.nonceStr, // 必填,生成簽名的隨機串
                signature: res.data.signature, // 必填,簽名
                jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareQZone', 'onMenuShareWeibo'] // 必填,須要使用的JS接口列表
            });

            wx.ready(function() {
                const title = '分享標題';
                const desc = '分享描述';
                const imgUrl = '分享圖片連接';
                // 朋友圈
                wx.onMenuShareTimeline({
                    title: title, // 分享標題
                    link: url, // 分享連接,該連接域名或路徑必須與當前頁面對應的公衆號JS安全域名一致
                    imgUrl: imgUrl // 分享圖標
                });

                // 微信朋友
                wx.onMenuShareAppMessage({
                    title: title, // 分享標題
                    desc: desc, // 分享描述
                    link: url, // 分享連接,該連接域名或路徑必須與當前頁面對應的公衆號JS安全域名一致
                    imgUrl: imgUrl, // 分享圖標
                    type: 'link', // 分享類型,music、video或link,不填默認爲link
                    dataUrl: '' // 若是type是music或video,則要提供數據連接,默認爲空
                });

                // qq
                wx.onMenuShareQQ({
                    title: title, // 分享標題
                    desc: desc, // 分享描述
                    link: url, // 分享連接
                    imgUrl: imgUrl // 分享圖標
                });

                // qq空間
                wx.onMenuShareQZone({
                    title: title, // 分享標題
                    desc: desc, // 分享描述
                    link: url, // 分享連接
                    imgUrl: imgUrl // 分享圖標
                });

                // 騰訊微博
                wx.onMenuShareWeibo({
                    title: title, // 分享標題
                    desc: desc, // 分享描述
                    link: url, // 分享連接
                    imgUrl: imgUrl // 分享圖標
                });
            });
        }
    });
複製代碼
相關文章
相關標籤/搜索