首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

ThinkPHP 實現微信 JSSDK 自定義分享:獲取簽名、配置權限、前端調用全流程

     編輯:PHP 綜合知識

前言

微信 JSSDK 允許網頁在微信客戶端內調用原生能力(分享、拍照、定位、支付等)。自定義分享是最常見的需求:將默認的「網頁標題+鏈接」替換為業務指定的標題、描述、圖片、鏈接。本文基於 ThinkPHP 框架,提供從後端獲取簽名到前端調用的完整實現方案。

核心流程

  1. 後端:用 appid + appsecret 獲取 access_token(緩存 7200 秒)。
  2. 後端:用 access_token 獲取 jsapi_ticket(緩存 7200 秒)。
  3. 後端:構造簽名字符串 jsapi_ticket=...&noncestr=...×tamp=...&url=...,SHA1 加密 得到 signature。
  4. 後端:將 appId、timestamp、nonceStr、signature 傳給前端模板。
  5. 前端:引入 jweixin-1.6.0.js,調用 wx.config() 注入權限,wx.ready() 中調用 wx.onMenuShareTimeline()、wx.onMenuShareAppMessage() 等分享接口。

後端實現(ThinkPHP 6/5.1 通用)

<?php
namespace app\controller;

use think\Controller;
use think\facade\Cache;
use think\facade\Request;

class WechatShare extends Controller {
    // 配置常量(建議放入 config/wechat.php)
    private const APPID     = 'YOUR_APPID';
    private const APPSECRET = 'YOUR_APPSECRET';
    
    /**
     * 分享頁面入口
     */
    public function index() {
        // 1. 獲取簽名所需參數
        $signData = $this->getSignPackage();
        
        // 2. 賦值給模板
        $this->assign([
            'appId'     => self::APPID,
            'timestamp' => $signData['timestamp'],
            'nonceStr'  => $signData['nonceStr'],
            'signature' => $signData['signature'],
            // 業務分享內容
            'shareTitle'   => '商品標題:超值大促鉅惠來襲',
            'shareDesc'    => '限時折扣,錯過不再有,快來搶購!',
            'shareLink'    => Request::url(true),  // 當前頁面完整 URL
            'shareImgUrl'  => 'https://cdn.example.com/share/cover.jpg',
        ]);
        
        return $this->fetch('share/index');
    }
    
    /**
     * 獲取 JSSDK 簽名包
     * @return array ['timestamp', 'nonceStr', 'signature']
     */
    private function getSignPackage(): array {
        $jsapiTicket = $this->getJsapiTicket();
        $url = Request::url(true);  // 當前頁面完整 URL(不含 #hash 部分)
        $timestamp = time();
        $nonceStr  = $this->randomStr(16);
        
        // 簽名字符串:參數按鍵名 ASCII 碼排序
        $string = "jsapi_ticket={$jsapiTicket}&noncestr={$nonceStr}×tamp={$timestamp}&url={$url}";
        $signature = sha1($string);
        
        return compact('timestamp', 'nonceStr', 'signature');
    }
    
    /**
     * 獲取 jsapi_ticket(緩存 7000 秒)
     */
    private function getJsapiTicket(): string {
        $cacheKey = 'wechat_jsapi_ticket_' . self::APPID;
        $ticket = Cache::get($cacheKey);
        if ($ticket) return $ticket;
        
        $accessToken = $this->getAccessToken();
        $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={$accessToken}&type=jsapi";
        $res = $this->httpGet($url);
        $data = json_decode($res, true);
        
        if (isset($data['errcode']) && $data['errcode'] !== 0) {
            throw new \RuntimeException("獲取 jsapi_ticket 失敗: {$data['errmsg']}");
        }
        
        $ticket = $data['ticket'];
        Cache::set($cacheKey, $ticket, 7000);  // 提前 200 秒過期
        return $ticket;
    }
    
    /**
     * 獲取 access_token(緩存 7000 秒)
     */
    private function getAccessToken(): string {
        $cacheKey = 'wechat_access_token_' . self::APPID;
        $token = Cache::get($cacheKey);
        if ($token) return $token;
        
        $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" . self::APPID . "&secret=" . self::APPSECRET;
        $res = $this->httpGet($url);
        $data = json_decode($res, true);
        
        if (isset($data['errcode']) && $data['errcode'] !== 0) {
            throw new \RuntimeException("獲取 access_token 失敗: {$data['errmsg']}");
        }
        
        $token = $data['access_token'];
        Cache::set($cacheKey, $token, 7000);
        return $token;
    }
    
    /**
     * 簡易 HTTP GET
     */
    private function httpGet(string $url): string {
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL            => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 10,
            CURLOPT_SSL_VERIFYPEER => false,  // 生產建議開啟並配置 CA
            CURLOPT_SSL_VERIFYHOST => false,
        ]);
        $response = curl_exec($ch);
        curl_close($ch);
        return $response;
    }
    
    /**
     * 生成隨機字符串
     */
    private function randomStr(int $length = 16): string {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $str = '';
        for ($i = 0; $i < $length; $i++) {
            $str .= $chars[random_int(0, strlen($chars) - 1)];
        }
        return $str;
    }
    
    /**
     * 判斷是否微信瀏覽器
     */
    public static function isWechat(): bool {
        return strpos($_SERVER['HTTP_USER_AGENT'] ?? '', 'MicroMessenger') !== false;
    }
}

前端模板(share/index.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <title>{$shareTitle}</title>
    <!-- 必須引入 JSSDK,支持 HTTPS -->
    <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
</head>
<body>
    <div class="container">
        <h1>{$shareTitle}</h1>
        <p>{$shareDesc}</p>
        <img src="{$shareImgUrl}" alt="分享圖片" style="max-width: 100%">
    </div>

    <script>
    // 1. 注入權限驗證配置
    wx.config({
        debug: false,  // 生產環境關閉,開啟後會 alert 返回信息
        appId: '{$appId}',
        timestamp: {$timestamp},
        nonceStr: '{$nonceStr}',
        signature: '{$signature}',
        // 必須聲明需要使用的接口列表
        jsApiList: [
            'onMenuShareTimeline',    // 分享到朋友圈
            'onMenuShareAppMessage',  // 分享給朋友
            'onMenuShareQQ',          // 分享到 QQ
            'onMenuShareWeibo',       // 分享到微博
            'updateAppMessageShareData',  // 1.4.0+ 新版接口
            'updateTimelineShareData'
        ]
    });

    // 2. 配置驗證成功後回調
    wx.ready(function() {
        // 通用分享數據
        const shareData = {
            title: '{$shareTitle}',
            desc: '{$shareDesc}',
            link: '{$shareLink}',
            imgUrl: '{$shareImgUrl}',
        };
        
        // 新版接口(推薦,兼容性更好)
        if (wx.updateAppMessageShareData) {
            wx.updateAppMessageShareData(shareData);
            wx.updateTimelineShareData(shareData);
        } else {
            // 舊版接口(兼容舊版微信)
            wx.onMenuShareAppMessage({
                ...shareData,
                success: () => console.log('分享給朋友成功'),
                cancel: () => console.log('取消分享給朋友'),
            });
            wx.onMenuShareTimeline({
                ...shareData,
                success: () => console.log('分享到朋友圈成功'),
                cancel: () => console.log('取消分享到朋友圈'),
            });
            wx.onMenuShareQQ({ ...shareData });
            wx.onMenuShareWeibo({ ...shareData });
        }
    });

    // 3. 配置驗證失敗回調
    wx.error(function(res) {
        console.error('JSSDK 配置失敗:', res);
        // 可上報錯誤到後端
    });
    </script>
</body>
</html>

關鍵坑點與避坑指南

問題原因解決
簽名驗證失敗 (invalid signature)URL 不一致:簽名用的 URL 必須與當前頁面完全一致(含協議、域名、路徑、查詢參數,不含 #hash)後端用 Request::url(true) 獲取完整 URL,前端不做任何跳轉/重寫
分享出去標題/圖片不生效未在 jsApiList 聲明接口、舊版接口已棄用、圖片域名未配置在「業務域名」白名單聲明所有用到的接口、使用新版 updateAppMessageShareData、配置業務域名
access_token/jsapi_ticket 失效緩存過期未刷新、並發請求導致重複獲取、Token 被其他服務器刷新覆蓋分布式環境用 Redis 分布式鎖刷新、單機用文件鎖、提前 200 秒主動刷新
Android/iOS 表現不一致微信客戶端版本差異、WebView 內核差異同時兼容新舊接口、實機測試多機型
HTTPS 證書問題開發環境無有效證書、自簽證書不被信任開發用內網穿透(ngrok/frp)映射到 HTTPS 域名、或配置信任自簽證書

分享域名配置

微信後台 → 公眾號設置 → 功能設置 → JS 接口安全域名(舊稱「業務域名」)。

  • 需備案、HTTPS、下載驗證文件放入網站根目錄。
  • 分享圖片 imgUrl 域名必須在該白名單內,否則圖片不顯示。

延伸閱讀

深入掌握:PHP 微信開發技巧匯總、ThinkPHP 6 完整開發手冊、微信 JSSDK 官方文檔 等專題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved