微信公眾號連結自定義分享總結

平凡的娃發表於2020-09-28

微信H5自定義分享頁面總結

微信公眾號中預設的分頁頁面很難看,預設的縮圖。工作需要將他自定義為想要的形式。
具體詳情檢視微信開發者文件:微信開發者文件

第一步、公眾號需要配置

1、需要一個公眾號的 appId、appSecret 只要有公眾號,那麼這個就有。
2、在該公眾號的js安全域名配置一個域名用來做分享出去的連結。(登入微信公眾平臺---->公眾號設定---->功能設定---->js安全域名)。

在這裡插入圖片描述

3、需要開通微信分享介面(登入微信公眾平臺----> 介面許可權 ---->分享介面)

在這裡插入圖片描述


第二步、後臺準備

臨時票據:jsapi_ticket
jsapi_ticket是公眾號用於呼叫微信JS介面的臨時票據。正常情況下,jsapi_ticket的有效期為7200秒。由於獲取jsapi_ticket的api呼叫次數非常有限,頻繁重新整理jsapi_ticket會導致api呼叫受限,影響自身業務,開發者必須在自己的服務全域性快取jsapi_ticket

1.通過下面連結能夠獲取access_token(get請求)(有效期7200秒,開發者必須在自己的服務全域性快取access_token):
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
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
}
3、獲得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…)拼接成字串

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.出於安全考慮,開發者必須在伺服器端實現簽名的邏輯。

4.要注意簽名演算法是否正確-----確認簽名演算法正確,可用http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 頁面工具進行校驗

總結一下就是:

先獲取:access_token
再獲取:jsapi_ticket
拼接字串後進行sha1加密得到:signature

程式碼大致如下(僅供參考):

一、fileCache.php

<?php
class FileCache
{
    /**
     * 快取目錄
     * @var
     */
    private $cache_dir;
    /**
     * @param $cache_dir
     * @throws Exception
     */
    public function __construct($cache_dir)
    {
        $this->cache_dir = $cache_dir;
        if (!is_dir($cache_dir)) {
            $make_dir_result = mkdir($cache_dir, 0755, true);
            if ($make_dir_result === false) throw new Exception('Cannot create the cache directory');
        }
    }
    /**
     * 根據key獲取值,會判斷是否過期
     * @param $key
     * @return mixed
     */
    public function get($key)
    {
        $cache_data = $this->getItem($key);
        if ($cache_data === false || !is_array($cache_data)) return false;
        return $cache_data['data'];
    }
    /**
     * 新增或覆蓋一個key
     * @param $key
     * @param $value
     * @param $expire
     * @return mixed
     */
    public function set($key, $value, $expire = 0)
    {
        return $this->setItem($key, $value, time(), $expire);
    }
    /**
     * 設定包含後設資料的資訊
     * @param $key
     * @param $value
     * @param $time
     * @param $expire
     * @return bool
     */
    private function setItem($key, $value, $time, $expire)
    {
        $cache_file = $this->createCacheFile($key);
        if ($cache_file === false) return false;
        $cache_data = array('data' => $value, 'time' => $time, 'expire' => $expire);
        $cache_data = json_encode($cache_data);

        $put_result = file_put_contents($cache_file, $cache_data);
        if ($put_result === false) return false;

        return true;
    }

    /**
     * 建立快取檔案
     * @param $key
     * @return bool|string
     */
    private function createCacheFile($key)
    {
        $cache_file = $this->path($key);
        if (!file_exists($cache_file)) {
            $directory = dirname($cache_file);
            if (!is_dir($directory)) {
                $make_dir_result = mkdir($directory, 0755, true);
                if ($make_dir_result === false) return false;
            }
            $create_result = touch($cache_file);
            if ($create_result === false) return false;
        }

        return $cache_file;
    }

    /**
     * 判斷Key是否存在
     * @param $key
     * @return mixed
     */
    public function has($key)
    {
        $value = $this->get($key);
        if ($value === false) return false;

        return true;
    }

    /**
     * 加法遞增
     * @param $key
     * @param int $value
     * @return mixed
     */
    public function increment($key, $value = 1)
    {
        $item = $this->getItem($key);
        if ($item === false) {
            $set_result = $this->set($key, $value);
            if ($set_result === false) return false;
            return $value;
        }

        $check_expire = $this->checkExpire($item);
        if ($check_expire === false) return false;

        $item['data'] += $value;

        $result = $this->setItem($key, $item['data'], $item['time'], $item['expire']);
        if ($result === false) return false;

        return $item['data'];
    }

    /**
     * 減法遞增
     * @param $key
     * @param int $value
     * @return mixed
     */
    public function decrement($key, $value = 1)
    {
        $item = $this->getItem($key);
        if ($item === false) {
            $value = 0 - $value;
            $set_result = $this->set($key, $value);
            if ($set_result === false) return false;
            return $value;
        }

        $check_expire = $this->checkExpire($item);
        if ($check_expire === false) return false;

        $item['data'] -= $value;

        $result = $this->setItem($key, $item['data'], $item['time'], $item['expire']);
        if ($result === false) return false;

        return $item['data'];
    }

    /**
     * 刪除一個key,同時會刪除快取檔案
     * @param $key
     * @return mixed
     */
    public function delete($key)
    {
        $cache_file = $this->path($key);
        if (file_exists($cache_file)) {
            $unlink_result = unlink($cache_file);
            if ($unlink_result === false) return false;
        }

        return true;
    }

    /**
     * 清楚所有快取
     * @return mixed
     */
    public function flush()
    {
        return $this->delTree($this->cache_dir);
    }

    /**
     * 遞迴刪除目錄
     * @param $dir
     * @return bool
     */
    function delTree($dir)
    {
        $files = array_diff(scandir($dir), array('.', '..'));
        foreach ($files as $file) {
            (is_dir("$dir/$file")) ? $this->delTree("$dir/$file") : unlink("$dir/$file");
        }
        return rmdir($dir);
    }

    /**
     * 根據key獲取快取檔案路徑
     *
     * @param  string $key
     * @return string
     */
    protected function path($key)
    {
        $parts = array_slice(str_split($hash = md5($key), 2), 0, 2);
        return $this->cache_dir . '/' . implode('/', $parts) . '/' . $hash;
    }

    /**
     * 獲取含有後設資料的資訊
     * @param $key
     * @return bool|mixed|string
     */
    protected function getItem($key)
    {
        $cache_file = $this->path($key);
        if (!file_exists($cache_file) || !is_readable($cache_file)) {
            return false;
        }

        $cache_data = file_get_contents($cache_file);
        if (empty($cache_data)) return false;
        $cache_data = json_decode($cache_data, true);
        if ($cache_data) {
            $check_expire = $this->checkExpire($cache_data);
            if ($check_expire === false) {
                $this->delete($key);
                return false;
            }
        }

        return $cache_data;
    }

    /**
     * 檢查key是否過期
     * @param $cache_data
     * @return bool
     */
    protected function checkExpire($cache_data)
    {
        $time = time();
        $is_expire = intval($cache_data['expire']) !== 0 && (intval($cache_data['time']) + intval($cache_data['expire']) < $time);
        if ($is_expire) return false;

        return true;
    }
}

二、KktWxjs.php

<?php
header('Access-Control-Allow-Origin:*');//允許跨域
header('Access-Control-Allow-Methods:POST,GET');//允許的請求
header("Content-Type:application/json/json");
require_once("fileCache.php");
class wxjs{
    private static $appId = "wxc*********21";
    private static $appSecret = "4c7**************79b";
	public function getTicekt(){
		$ticket = '';
        $diffTime  = 0;
		//現在的時間戳
        $date = strtotime(date("Y-m-d H:i:s"));
		//獲取快取資訊
        $cache= new FileCache('cache');
        if($cache->has('jsapi_ticket')){
        	$ticket = $cache->get('jsapi_ticket');
        	$expiresIn = $cache->get('expires_in')-10;//過期時間
        	$create_time = $cache->get('create_time');//上次更新的時間
        	$diffTime = $date - strtotime($create_time);
        }

        if ($diffTime >= $expiresIn || !$ticket) {//判斷是否過期
            $appId = wxjs::$appId; //微信appid
            $appSecret = wxjs::$appSecret; //微信金鑰
			
            // 參考以下文件獲取access_token(有效期7200秒,開發者必須在自己的服務全域性快取access_token)
            // https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
            $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" . $appId . "&secret=" . $appSecret; //獲取token的url
            $dataToken = wxjs::requestGet($url); //curl獲取access_token
            $token = $dataToken['access_token']; //新的access_token
            $expires  = $dataToken['expires_in']; //新的過期時間

            // 用第一步拿到的access_token 採用http GET方式請求獲得jsapi_ticket(有效期7200秒,開發者必須在自己的服務全域性快取jsapi_ticket):
            // https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
            $urlTicket  = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" . $token . "&type=jsapi"; //獲取ticket的url
            $dataTicket = wxjs::requestGet($urlTicket);
            $ticket     = $dataTicket['ticket'];

            // 服務全域性快取jsapi_ticket 、expires_in 、create_time
            $cache->set("jsapi_ticket",$ticket);
            $cache->set("expires_in",$expires);
            $cache->set("create_time",$create_time = date("Y-m-d H:i:s"));
        } else {
            $ticket = $ticket;
        }
        return $ticket;
    }

    // 通過該介面獲取access_token、和 jsapi_ticket
	public static function requestGet($url = '')
    {
        if (empty($url)) {
            return false;
        }
        $curl = curl_init(); //初始化
        curl_setopt($curl, CURLOPT_URL, $url); //設定抓取的url
        curl_setopt($curl, CURLOPT_HEADER, 0); //設定標頭檔案的資訊作為資料流輸出
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); //設定獲取的資訊以檔案流的形式返回,而不是直接輸出。
        $data = curl_exec($curl); //執行命令
        curl_close($curl); //關閉URL請求
        $data = json_decode($data, true);
        return $data;
    }

    // 獲得jsapi_ticket之後,就可以生成JS-SDK許可權驗證的簽名了。
    public function wxDataFormat($url){
    	$str="QWER*************7890";
        $nonce = str_shuffle($str);//隨機字串
        $timestamp = strtotime(date("Y-m-d H:i:s")); //現在的時間戳
        $ticket = $this->getTicekt();
        $str = "jsapi_ticket=".$ticket."&noncestr=".$nonce."&timestamp=".$timestamp."&url=".$url;
        $signature = sha1($str);
        // echo $str;
        $data = array("noncestr"=>$nonce,"timestamp"=>$timestamp,"signature"=>$signature);
        return $data;
    }

}

$poststr = file_get_contents("php://input");
$post = json_decode($poststr,true);
$url = $post['url'];
$wx = new wxjs();
$data = $wx->wxDataFormat($url);
$data = json_encode($data,true);
$response = '{"data":'.$data.',"statusMsg":"成功","statusCode":200}';
echo $response;

三、 第三步前端工作

通過介面獲取配置資訊 —用來簽名 -----通過config介面注入許可權驗證配置

Axios.post("http://******/WXJsInfo.php", {
  url: window.location.href.split("#")[0]
})
  .then(res => {
    let data = res.data.data; // PS: 這裡根據你介面的返回值來使用
    wx.config({
      debug: false, // 開啟除錯模式
      appId: "wx9********5cf", // 必填,公眾號的唯一標識
      timestamp: data.timestamp, // 必填,生成簽名的時間戳
      nonceStr: data.noncestr, // 必填,生成簽名的隨機串
      signature: data.signature, // 必填,簽名,見附錄1
      jsApiList: [
        "updateAppMessageShareData",
        "updateTimelineShareData",
        "onMenuShareAppMessage",
        "onMenuShareTimeline",
        "hideMenuItems"
      ] // 必填,需要使用的JS介面列表,所有JS介面列表見附錄2
    }); 20.
  })

2.通過wx.ready來檢查簽名是否成功

wx.ready(function(){
   注意:
  // config資訊驗證後會執行ready方法,所有介面呼叫都必須在config介面獲得結果之後,config是一個客戶端的非同步操作,所以如果需要在頁面載入時就呼叫相關介面,則須把相關介面放在ready函式中呼叫來確保正確執行。對於使用者觸發時才呼叫的介面,則可以直接呼叫,不需要放在ready函式中。
});

wx.error(function(res){
  // config資訊驗證失敗會執行error函式,如簽名過期導致驗證失敗,具體錯誤資訊可以開啟config的debug模式檢視,也可以在返回的res引數中檢視,對於SPA可以在這裡更新簽名。
});

3.檢查是否用權利呼叫相應的介面

wx.checkJsApi({
  jsApiList: ['chooseImage'], // 需要檢測的JS介面列表,所有JS介面列表見附錄2,
  success: function(res) {
  // 以鍵值對的形式返回,可用的api值true,不可用為false
  // 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"}
  }
});

完整程式碼:

wxShare.js

const wxShare = {
  wxRegister(axiosUrl,title, desc, link, imgUrl) {
    //axiosUrl:後端提供的連結,用來獲取JS-SDK許可權驗證的簽名等資訊  這裡就是http://*******/KktWxjs.php檔案 (必須與當前頁面同源)
    //title:自定義名稱
    //des:自定義描述
    //link:分享的連結(必須是js安全域名)
    //imgUrl:圖片連結
    axios.post(axiosUrl, {
      url: window.location.href
    })
      .then(res => {
        let data = res.data.data; // PS: 這裡根據你介面的返回值來使用{noncestr:"R7AG3**************YCVLPH2W8UI",signature:"765f0618f********82f7e2621d2e59c",timestamp:160***302}
        wx.config({
          debug: false, // 開啟除錯模式
          appId: "wxcc617f1932e74a21", // 必填,公眾號的唯一標識
          timestamp: data.timestamp, // 必填,生成簽名的時間戳
          nonceStr: data.noncestr, // 必填,生成簽名的隨機串
          signature: data.signature, // 必填,簽名,見附錄1
          jsApiList: [
            "updateAppMessageShareData",
            "updateTimelineShareData",
            "onMenuShareAppMessage",
            "onMenuShareTimeline",
            "hideMenuItems"
          ] // 必填,需要使用的JS介面列表,所有JS介面列表見附錄2
        });
        wx.checkJsApi({
          jsApiList: ["onMenuShareAppMessage", "onMenuShareTimeline"], // 需要檢測的JS介面列表,所有JS介面列表見附錄2,
          success: function (res) {
            // 以鍵值對的形式返回,可用的api值true,不可用為false
            // 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"}
            console.log(res);
            // 分享給好友
            wx.onMenuShareAppMessage({
              title: title,
              desc: desc,
              link: link, //必須和當前頁面同源
              imgUrl: imgUrl,
              trigger: function () {
                // 不要嘗試在trigger中使用ajax非同步請求修改本次分享的內容,因為客戶端分享操作是一個同步操作,這時候使用ajax的回包會還沒有返回
                // alert("使用者點選傳送給朋友");
              },
              success: function () {
                alert("分享成功");
                that.hideMask = "none";
                that.clientCount = 0;
                that.requestClientCount(0);
              },
              cancel: function () {
                alert("分享失敗");
              },
              fail: function () {
                alert("分享失敗");
              }
            });
            // 分享給朋友圈
            wx.onMenuShareTimeline({
              title: title,
              link: link,
              imgUrl: imgUrl,
              trigger: function () {
                // 不要嘗試在trigger中使用ajax非同步請求修改本次分享的內容,因為客戶端分享操作是一個同步操作,這時候使用ajax的回包會還沒有返回
                // alert("使用者點選傳送給朋友");
              },
              success: function () {
                alert("分享成功");
                setTimeout(function () {
                  //回撥要執行的程式碼
                  that.hideMask = "none";
                  that.clientCount = 0;
                  that.requestClientCount(0);
                }, 500);
              },
              cancel: function () {
                alert("分享失敗");
              },
              fail: function () {
                alert("分享失敗");
              }
            });
          }
        });
        //簽名成功後呼叫的方法
        wx.ready(function () {
          //需在使用者可能點選分享按鈕前就先呼叫
          // alert("簽名成功");

        });
        // 簽名失敗呼叫的介面
        wx.error(function () {
          // config資訊驗證失敗會執行error函式,如簽名過期導致驗證失敗,具體錯誤資訊可以開啟config的debug模式檢視,也可以在返回的res引數中檢視,對於SPA可以在這裡更新簽名。
          alert("簽名失敗");
        });
      })
      .catch(error => {
        console.log("配置出錯", error);
      });
  }
};

呼叫

<script src="http://******/wxShare/weixin-js-sdk.js"></script>
<script src="http://******/wxShare/axios.js"></script>
<script src="http://******/wxShare/wxShare.js"></script>
//自定義微信分享
wxShare.wxRegister('http://******/KktWxjs.php','自定義名稱','自定義描述','自定義連結',"自定義圖片連結");

相關文章