簡述H5頁面在手機瀏覽器實現微信分享


一、調研結果

  • 微信內置瀏覽器進行分享,只能監聽微信自帶的分享按鈕,自定義分享的圖標什么的,不可能主動觸發分享,可以引用微信公眾平台的自定義分享接口,也就是JSSDK的相關API,文檔地址如下:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.htm
    • 在微信公眾平台注冊一個公眾號(必須為企業資質的訂閱號,並且開通分享接口的權限:需要企業認證並繳費);開發---接口權限
    • 設置---公眾號設置---功能設置,填寫有效的JS接口安全域名;
    • 開發---基本設置---IP白名單,填寫項目所在的服務器IP地址;
    • 在vue項目中引入jssdk,微信為了方便用戶使用,將官方的JSSDK發布到了npm上,有一個叫weixin-js-sdk的是針對CommonJs規范提出的,需要使用require引入;另一個是叫weixin-jsapi,是針對ES6提出的,這個時候我們可以使用import方式引入;
    • 出於安全考慮,服務端獲取簽名:
    • 獲取access_token,有效期7200秒,在服務端進行緩存,請求地址為:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
    • 通過第一步拿到的access_token獲取jsapi_ticket,有效期7200秒,在服務端進行緩存,請求地址為:https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
    • noncestr(隨機字符串), 有效的jsapi_ticket,timestamp(時間戳), url(當前網頁的URL,不包含#及其后面部分)按照ASCII碼從小到大排序,組織成URL鍵值對的形式,並對整個字符串進行sha1加密,生成簽名;
    • 簽名地址:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62
    • 簽名流程圖:
    • 拿到后台返回的參數,在config里面進行配置:
         wx.config({
            debug: true, // 開啟調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時才會打印。
            appId: AppId, // 必填,公眾號的唯一標識
            timestamp: Timestamp, // 必填,生成簽名的時間戳
            nonceStr: NonceStr, // 必填,生成簽名的隨機串
            signature: Signature, // 必填,簽名
            jsApiList: [
              //JSSDK1.4以后,微信分享功能用新接口,但是在接口注冊的時候,必須把新老接口都加上去,不然不起作用
              'checkJsApi',
              'onMenuShareTimeline', //分享到微信朋友圈
              'onMenuShareAppMessage', //分享給微信朋友
              'onMenuShareQQ', //分享到QQ
              'onMenuShareQZone', //分享到QQ空間
              'updateAppMessageShareData', //分享到微信及QQ(新接口)
              'updateTimelineShareData' //分享到朋友圈”及“分享到QQ空間(新接口
            ] // 必填,需要使用的JS接口列表
          });
    • 在wx.ready函數里調用jsApiList參數里面配置的相關api:
          //通過ready接口處理成功驗證
          // config信息驗證后會執行ready方法,所有接口調用都必須在config接口獲得結果之后,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,
          // 則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。
          wx.ready(function() {
            wx.updateAppMessageShareData(shareInfo); //分享到微信好友或者qq好友
            wx.updateTimelineShareData(shareInfo); //分享到朋友圈或者qq空間
          });
      
          wx.error(function(res) {
            // config信息驗證失敗會執行error函數,如簽名過期導致驗證失敗,具體錯誤信息可以打開config的debug模式查看,也可以在返回的res參數中查看,對於SPA可以在這里更新簽名。
            console.log(res);
      console.log(res.errMsg); });
  • UC以及QQ瀏覽器的分享:由於UC以及QQ兩個APP具有微信分享的能力,而兩個APP又將微信分享下放到相關頁面,因此頁面也具有分享到微信的能力;
    • ios系統的UC瀏覽器10.2版本以下不具有分享到微信的能力;
    • android系統的UC瀏覽器9.7版本以下不具有分享到微信的能力;
    • ios系統的QQ瀏覽器5.4版本以下不具有分享到微信的能力;
    • android系統的QQ瀏覽器5.3版本以下不具有分享到微信的能力;
    • android系統的QQ瀏覽器5.4以下以及5.3以上屬於低版本瀏覽器,需要加載低版本bridgeapi:http://3gimg.qq.com/html5/js/qb.js 其余版本需要加載高版本的bridgeapi:http://jsapi.qq.com/get?api=app.share
    • 注意:UC瀏覽器會直接使用截圖進行分享,不支持用戶自定義圖片;
  • 其他瀏覽器分享:由於這些瀏覽器的頁面不具有分享到微信的能力,因此需要業務組件開發時進行”引導式分享“,即可以通過點擊H5頁面元素,顯示一個引導圖,引導用戶使用右上角的轉發功能;

二、具體實現

  1. 實現的功能
    9.7版本以上的UC瀏覽器在android端,10.2版本以上的UC瀏覽器在ios端實現分享到微信以及微信朋友圈;
    5.3版本以上的QQ瀏覽器在android端,5.4以上的QQ瀏覽器在ios端實現分享到微信以及微信朋友圈;
    微信內置瀏覽器實現自定義分享到微信以及微信朋友圈;
  • 實現代碼
    import { promiseAjax } from '../../utils/promiseAjax';
    // import wx from 'weixin-jsapi';
    import wx from 'weixin-js-sdk';
    
    /**
     * @WeChat 創建一個微信分享的父類WeChat
     */
    class WeChat {
      constructor() {
        this.UA = navigator.appVersion;
        this.browserPermission = {
          UC: { forbid: 0, allow: 1 },
          QQ: { forbid: 0, lower: 1, higher: 2 }
        };
        this.qqApiSrc = {
          lower: 'http://3gimg.qq.com/html5/js/qb.js',
          higher: 'http://jsapi.qq.com/get?api=app.share'
        };
        this.qqBridgeLoaded = false; //qq瀏覽器下面是否加載好了相應的api文件
        this.config = {}; //默認的config數據
      }
      /**
       * @getOs 判斷操作系統的類型
       * @return {String} IOS、ANDROID與WEB
       */
      getOs() {
        if (/(iPhone|iPad|iPod|iOS)/i.test(this.UA)) {
          return 'IOS';
        } else if (/(Android)/i.test(this.UA)) {
          return 'ANDROID';
        } else {
          return 'WEB';
        }
      }
      /**
       * @isUcBrowser 判斷是否是UC瀏覽器
       * @return {Number} 0表示禁止  1表示允許
       */
      isUCBrowser() {
        if (/UCBrowser/i.test(this.UA)) {
          if (
            (this.getOs() == 'IOS' && this.getVersion('UCBrowser/') < 10.2) ||
            (this.getOs() == 'ANDROID' && this.getVersion('UCBrowser/') < 9.7)
          ) {
            return this.browserPermission.UC.forbid;
          } else {
            return this.browserPermission.UC.allow;
          }
        } else {
          return this.browserPermission.UC.forbid;
        }
      }
      /**
       * @isUcBrowser 判斷是否是QQ瀏覽器
       * @return {Number} 0表示禁止 1表示低版本允許  2表示高版本允許
       */
      isQQBrowser() {
        if (/MQQBrowser/i.test(this.UA)) {
          if (
            (this.getOs() == 'IOS' && this.getVersion('MQQBrowser/') < 5.4) ||
            (this.getOs() == 'ANDROID' && this.getVersion('MQQBrowser/') < 5.3)
          ) {
            return this.browserPermission.QQ.forbid;
          } else {
            if (this.getOs() == 'ANDROID' && this.getVersion('MQQBrowser/') < 5.4) {
              return this.browserPermission.QQ.lower;
            } else {
              return this.browserPermission.QQ.higher;
            }
          }
        } else {
          return this.browserPermission.QQ.forbid;
        }
      }
      /**
       * @isWXBrowser 判斷是否是微信內置瀏覽器
       * @return {Boolean} true表示是  false表示不是
       */
      isWXBrowser() {
        return /MicroMessenger/i.test(this.UA);
      }
      /**
       * @getVersion 獲取瀏覽器的版本號
       * @param {String} sign UCBrowser/MQQBrowser
       * @return {Number}瀏覽器的版本號
       */
      getVersion(sign) {
        return parseFloat(this.UA.split(sign)[1]);
      }
      /**
       * @UCShare UC瀏覽器的分享:會直接使用截圖
       * @return {*}
       */
      UCShare() {
        // ios 對象:ucbrowser 微信好友:kWeixin 微信朋友圈:kWeixinFriend
        // android  對象:ucweb 微信好友:WechatFriends 微信朋友圈: WechatTimeline
        // ['title', 'content', 'url', 'platform', 'disablePlatform', 'source', 'htmlID']
        let platform =
          this.getOs() == 'IOS' && this.config.type == 1
            ? 'kWeixin'
            : this.getOs() == 'IOS' && this.config.type == 2
            ? 'kWeixinFriend'
            : this.getOs() == 'ANDROID' && this.config.type == 1
            ? 'WechatFriends'
            : this.getOs() == 'ANDROID' && this.config.type == 2
            ? 'WechatTimeline'
            : this.throwError();
        let shareInfo = [
          this.config.title,
          this.config.description,
          this.config.url,
          platform,
          '',
          '',
          ''
        ];
        if (
          this.getOs() == 'ANDROID' &&
          window.ucweb &&
          window.ucweb.startRequest
        ) {
          window.ucweb.startRequest('shell.page_share', shareInfo);
          return;
        } else if (
          this.getOs() == 'IOS' &&
          window.ucbrowser &&
          window.ucbrowser.web_share
        ) {
          window.ucbrowser.web_share.apply(null, shareInfo);
          return;
        } else {
          this.throwError();
        }
      }
      /**
       * @QQShare QQ瀏覽器的分享  微信好友:1 微信朋友圈:8
       * @return {*}
       */
      QQShare(config) {
        let type = this.config.type == 1 ? 1 : 8;
        var share = function() {
          let shareInfo = {
            title: config.title,
            description: config.description,
            url: config.url,
            img_url: config.mediaData,
            img_title: config.title,
            to_app: type,
            cus_txt: ''
          };
          if (window.browser && window.browser.app) {
            window.browser.app.share(shareInfo);
          } else if (window.qb && window.qb.share) {
            window.qb.share(shareInfo);
          } else {
            let errMsg = {
              moduleName: 'WXShare',
              interfaceName: 'WXShare',
              errorCode: 'native_share_N001',
              errorMessage: '瀏覽器不支持進行微信分享'
            };
            throw new InteractiveError(errMsg);
          }
        };
        this.qqBridgeLoaded ? share() : this.loadQQApi(share);
      }
      /**
       * @loadQQApi  qq瀏覽器根據不同版本加載對應的bridge
       * @param {Function} cb 回調函數
       * @return {*}
       */
      loadQQApi(cb) {
        var qqApiScript = document.createElement('script');
        /**
         * 需要等加載過 qq 的 bridge 腳本之后
         * 再去初始化分享組件
         */
        qqApiScript.onload = function() {
          cb && cb();
        };
        qqApiScript.src =
          this.isQQBrowser() == 1 ? this.qqApiSrc.lower : this.qqApiSrc.higher;
        document.body.appendChild(qqApiScript);
      }
      /**
       * @wxShare 微信內置瀏覽器的分享
       * @param {*}
       * @return {*}
       */
      async wxShare() {
        // 首先通過config接口注入權限驗證配置
        let url = 'xxx'
        let data = {
          headers: {},
          body: {
            Url:
              this.getOs() == 'IOS'
                ? '入口地址'
                : window.location.href.split('#')[0]
          }
        };
        let config = await promiseAjax('post', url, data);
        const { AppId, Timestamp, NonceStr, Signature } = config;
        wx.config({
          debug: true, // 開啟調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時才會打印。
          appId: AppId, // 必填,公眾號的唯一標識
          timestamp: Timestamp, // 必填,生成簽名的時間戳
          nonceStr: NonceStr, // 必填,生成簽名的隨機串
          signature: Signature, // 必填,簽名
          jsApiList: [
            //JSSDK1.4以后,微信分享功能用新接口,但是在接口注冊的時候,必須把新老接口都加上去,不然不起作用
            'checkJsApi',
            'onMenuShareTimeline', //分享到微信朋友圈
            'onMenuShareAppMessage', //分享給微信朋友
            'onMenuShareQQ', //分享到QQ
            'onMenuShareQZone', //分享到QQ空間
            'updateAppMessageShareData', //分享到微信及QQ(新接口)
            'updateTimelineShareData' //分享到朋友圈”及“分享到QQ空間(新接口
          ] // 必填,需要使用的JS接口列表
        });
        let shareInfo = {
          title: this.config.title, // 分享標題
          desc: this.config.description, // 分享描述
          link: this.config.url, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
          type: 'link', //分享類型,music、video或link,不填默認為link
          imgUrl: this.config.mediaData, // 分享圖標
          success: function() {
            // 設置成功
            console.log('分享成功');
          },
          error: function() {
            console.log('分享失敗');
          },
          cancel: function() {
            console.log('取消分享');
          }
        };
        //通過ready接口處理成功驗證
        // config信息驗證后會執行ready方法,所有接口調用都必須在config接口獲得結果之后,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,
        // 則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。
        wx.ready(function() {
          wx.updateAppMessageShareData(shareInfo); //分享到微信好友或者qq好友
          wx.updateTimelineShareData(shareInfo); //分享到朋友圈或者qq空間
        });
    
        wx.error(function(res) {
          // config信息驗證失敗會執行error函數,如簽名過期導致驗證失敗,具體錯誤信息可以打開config的debug模式查看,也可以在返回的res參數中查看,對於SPA可以在這里更新簽名。
          console.log(res);
        });
      }
      /**
       * @throwError 交互類異常
       * @param {*}
       * @return {*}
       */
      throwError() {
        throw 瀏覽器不支持進行微信分享;
      }
      /**
       * @share 進行分享
       * @param {Object} param 分享的參數對象
       * @return {*}
       */
      share(config) {
        this.config = config;
        if (this.config.type == undefined) {
          throw type是空值;
        }
        this.isUCBrowser()
          ? this.UCShare()
          : this.isQQBrowser() && !this.isWXBrowser()
          ? this.QQShare(this.config)
          : this.isWXBrowser() && this.isQQBrowser() && this.getOs() == 'ANDROID'
          ? this.wxShare()
          : this.isWXBrowser() && this.getOs() == 'IOS'
          ? this.wxShare()
          : this.throwError();
      }
    }
    
    /**
     * @WXShare 創建一個微信分享的實例
     * @return {*}
     */
    export const WXShare = new WeChat();
    
    /**
     * @description 預加載qqbridge
     */
    WXShare.loadQQApi(function() {
      WXShare.qqBridgeLoaded = true;
    });
    View Code
  • promise封裝一個ajax請求
    /**
     * @promiseAjax 給后台發請求
     * @param {String} method 請求的方式
     * @param {String} path 請求的url
     * @param {Object} body post方式傳遞給后台的數據
     * @return {Promise}  返回一個promise對象
     */
    export function promiseAjax(method, path, body) {
      return new Promise((resolve, reject) => {
        var xhr = '';
        if (window.XMLHttpRequest) {
          xhr = new XMLHttpRequest();
        } else {
          xhr = new window.ActiveXObject('Microsoft.XMLHTTP'); // IE6瀏覽器創建ajax對象
        }
        xhr.open(method, path);
        xhr.send(JSON.stringify(body));
        xhr.onreadystatechange = function() {
          if (xhr.readyState == 4 && xhr.status == 200) {
            resolve(JSON.parse(xhr.responseText));
          }
          setTimeout(() => {
            reject(new Error(xhr.statusText));
          }, 3000);
        };
      });
    }
  • 前端的調用方式
         let config = {
            title: '測試用例',
            desc: '你看這個行不行',
            mediaData:
              'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1603186815698&di=7ec300630a404299c855c73a99773e17&imgtype=0&src=http%3A%2F%2Fcdn.duitang.com%2Fuploads%2Fitem%2F201411%2F04%2F20141104225457_f8mrM.thumb.700_0.jpeg',
            url: window.location.href,
            type: type
          };
         await WXShare.share(config);

三、參考文檔

  1. https://www.cnblogs.com/backtozero/p/7064247.html
  2. https://blog.csdn.net/lgj199505/article/details/103520329?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control
  3. https://segmentfault.com/a/1190000037552782?utm_source=tag-newest

四、遇到的問題

  • invalid signature
    1、確認簽名算法正確,可用http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 頁面工具進行校驗;
    2、傳給后端的url必須為動態獲取,如果后端用decodeURLComponent對url進行解碼,我們傳的url必須通過encodeURLComponent進行編碼;
    3、
    4、確認config中nonceStr(js中駝峰標准大寫S), timestamp與用以簽名中的對應noncestr, timestamp一致;
    5.請確保后台返回的appid與你自己關注的公眾號相一致
  • IOS端二次分享簽名失敗,報invalid signature
    • 原因:

      ios設備傳的地址為首次進入應用的地址(入口地址),安卓設備為分享頁面的地址,以下進行詳細解釋;

         Vue-Router進行路由切換的時候,總是會操作瀏覽器的歷史記錄,從而響應頁面URL變化。

            在JSSDK文檔頁面有這么一句話:同一個url僅需調用一次,對於變化url的SPA的web app可在每次url變化時進行調用,目前Android微信客戶端不支持pushState的H5新特性,所以使用pushState來實現web app的頁面會導致簽名失敗,此問題會在Android6.2中修復。但根據多次測試情況來看,情況恰好相反,在Android下直接使用 window.location.href 得出的URL進行簽名是完全沒問題(可能已升級至Android6.2以上版本),在IOS上就不行了。這是因為在IOS上,無論路由切換到哪個頁面,實際真正有效的的簽名URL是【第一次進入應用時的URL】。比如進入應用首頁是: https://m.app.com,需要使用JSSDK的頁面A是:https://m.app.com/product1/123,無論從首頁進入到A頁面之前,中間跳轉過多少次路由,最終簽名有效的URL還是首頁URL。

    • 解決辦法:
      let signUrl = '';
      signUrl;
      function getOs() {
        if (/(iPhone|iPad|iPod|iOS)/i.test(window.navigator.appVersion)) {
          return 'IOS'
        } else {
          return 'ANDROID'
        }
      }
      
      // 由於項目是基於所有的頁面都需要分享,因此每個頁面都進行配置是不切實際的,因此我們希望在vue的路由守衛去完成,beforeEach守衛
      // 會導致頁面申請簽名的時候還是上一個頁面,但是到了新頁面又沒有注冊簽名,導致invalid signature
      router.afterEach((to, from, next) => {
        setTimeout(async () => {
          if (getOs() === 'IOS') {
            if (window.entryUrl === '' || window.entryUrl === undefined) {
              window.entryUrl = window.location.href
            }
            signUrl = window.entryUrl
          } else {
            // 安卓機  ${project.context}指的是項目的名稱
            signUrl = `${window.location.origin}/${project.context}${to.fullpath}`
          }
          await config({
            signUrl: signUrl
          })
        }, 1000)
      })
  • require subscribe ----- 這個問題要關注相對應的公眾號;
  • 當你確保簽名算法以及url地址正確后,那大部分都是后台環境的問題,遇到錯誤之后與后台人員協商一步步找問題,祝好運~~~
  • 開啟debug測試得出,IOS手機:先報錯:the permission value is offline verifying,在彈出config:ok,官方給出的解決方案是
    這個錯誤是因為config沒有正確執行,或者是調用的JSAPI沒有傳入config的jsApiList參數中。建議按如下順序檢查:
    1、確認config正確通過
    2、如果是在頁面加載好時就調用了JSAPI,則必須寫在wx.ready的回調中
    3、確認config的jsApiList參數包含了這個JSAPI

    但是我的代碼完全是按照這種方式寫的,所以官網沒有解決我的問題,所以在查看了其他文檔之后,決定采用了定時器,完美解決.

    setTimeout(()=>{
       wx.ready(function(){
          wx.hideAllNonBaseMenuItem()
       })
    },300)
  • 遇到問題后會持續補充......

 


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM