1. 理解微信网页分享的原理
  2. 准备工作:获取必要的凭证
  3. 后端 PHP 开发:生成签名
  4. 前端页面集成:调用 JS 接口
  5. 常见问题与最佳实践

理解微信网页分享的原理

微信的 JSSDK (JavaScript SDK) 并不能直接让你自定义分享标题、描述和图片,它的核心流程是:

php 微信网页分享接口开发平台
(图片来源网络,侵删)
  1. 你的服务器向微信服务器请求权限:你的 PHP 后端需要向微信服务器发送请求,获取一个临时的 access_token
  2. 生成签名:使用 access_tokenjsapi_ticket(另一个需要通过 access_token 获取的临时凭证)、URL(当前页面的完整URL)等信息,按照微信指定的算法生成一个 signature 签名。
  3. 后端将签名等参数传递给前端:你的 PHP 页面将 appId, timestamp, nonceStr, signature 这四个关键参数传递给前端的 HTML/JavaScript 代码。
  4. 前端调用微信 JS 接口:前端 JavaScript 使用 wx.configwx.ready 方法来配置和调用微信的分享接口(如 updateAppMessageShareData)。
  5. 用户分享:当用户在微信内置浏览器中点击右上角的“...”菜单,选择“分享给朋友”或“分享到朋友圈”时,就会把你设置好的标题、描述、图片和链接分享出去。

关键点:所有敏感操作(如获取 access_tokenjsapi_ticket)都在你自己的服务器(PHP)上完成,只将安全的签名参数传递给前端,这保证了你的 AppSecret 等核心密钥不会泄露。


准备工作:获取必要的凭证

在开始编码之前,你必须拥有以下信息:

  1. 一个已认证的微信公众号:必须是服务号(订阅号没有网页分享权限),并且已开通“网页授权”和“JS接口安全域名”。
  2. AppID (应用ID):在公众号后台“开发者中心”可以找到。
  3. AppSecret (应用密钥):同样在“开发者中心”可以找到,这个非常重要,绝不能泄露!
  4. 设置 JS接口安全域名
    • 登录微信公众平台。
    • 进入“设置与开发” -> “公众号设置” -> “功能设置”。
    • 在“JS接口安全域名”一栏,添加你的网站域名(www.yourdomain.com)。
    • 注意:域名需要通过备案,且添加后需要一段时间(几分钟到几小时)才能生效,http 请求会自动跳转为 https,所以确保你的服务器支持 SSL。

后端 PHP 开发:生成签名

这是整个开发的核心,我们需要创建一个 PHP 类来管理 access_tokenjsapi_ticket,并提供生成签名的方法。

步骤 3.1:创建一个工具类 WeChatHelper.php

这个类将负责:

php 微信网页分享接口开发平台
(图片来源网络,侵删)
  • 缓存 access_tokenjsapi_ticket(因为它们是有时效性的,避免频繁请求微信服务器)。
  • 从微信服务器获取新的 access_tokenjsapi_ticket
  • 生成最终的分享签名。
// WeChatHelper.php
class WeChatHelper
{
    private $appId;
    private $appSecret;
    private $accessTokenCacheKey = 'wechat_access_token';
    private $jsapiTicketCacheKey = 'wechat_jsapi_ticket';
    private $cacheExpireTime = 7000; // token 有效期为 7200 秒,我们提前 200 秒过期
    public function __construct($appId, $appSecret)
    {
        $this->appId = $appId;
        $this->appSecret = $appSecret;
    }
    /**
     * 获取 Access Token
     */
    public function getAccessToken()
    {
        // 1. 尝试从缓存获取
        $cachedToken = $this->getCache($this->accessTokenCacheKey);
        if ($cachedToken) {
            return $cachedToken;
        }
        // 2. 缓存中没有,则从微信服务器获取
        $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appId}&secret={$this->appSecret}";
        $response = $this->httpRequest($url);
        if (isset($response['access_token'])) {
            // 3. 存入缓存
            $this->setCache($this->accessTokenCacheKey, $response['access_token'], $this->cacheExpireTime);
            return $response['access_token'];
        }
        return false;
    }
    /**
     * 获取 Jsapi Ticket
     */
    public function getJsapiTicket()
    {
        // 1. 尝试从缓存获取
        $cachedTicket = $this->getCache($this->jsapiTicketCacheKey);
        if ($cachedTicket) {
            return $cachedTicket;
        }
        // 2. 缓存中没有,则用 access_token 去获取
        $accessToken = $this->getAccessToken();
        if (!$accessToken) {
            return false;
        }
        $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token={$accessToken}";
        $response = $this->httpRequest($url);
        if (isset($response['ticket'])) {
            // 3. 存入缓存
            $this->setCache($this->jsapiTicketCacheKey, $response['ticket'], $this->cacheExpireTime);
            return $response['ticket'];
        }
        return false;
    }
    /**
     * 生成 JS-SDK 签名
     */
    public function generateSignature($url)
    {
        $ticket = $this->getJsapiTicket();
        if (!$ticket) {
            return false;
        }
        $timestamp = time();
        $nonceStr = $this->createNonceStr();
        // 字典序排序参数
        $string = "jsapi_ticket={$ticket}&noncestr={$nonceStr}&timestamp={$timestamp}&url={$url}";
        $signature = sha1($string);
        return [
            'appId'     => $this->appId,
            'timestamp' => $timestamp,
            'nonceStr'  => $nonceStr,
            'signature' => $signature,
        ];
    }
    /**
     * 生成随机字符串
     */
    private function createNonceStr($length = 16)
    {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }
    /**
     * HTTP 请求
     */
    private function httpRequest($url, $data = null)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
        if ($data) {
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        }
        $output = curl_exec($ch);
        curl_close($ch);
        return json_decode($output, true);
    }
    /**
     * 模拟缓存操作 (实际项目中请使用 Redis, Memcached, 文件缓存等)
     */
    private function getCache($key)
    {
        // 这里简化为使用文件缓存,生产环境建议使用更高效的缓存
        $cacheFile = __DIR__ . '/cache/' . md5($key) . '.cache';
        if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $this->cacheExpireTime) {
            return file_get_contents($cacheFile);
        }
        return false;
    }
    private function setCache($key, $value, $expire)
    {
        // 确保缓存目录存在
        $cacheDir = __DIR__ . '/cache/';
        if (!is_dir($cacheDir)) {
            mkdir($cacheDir, 0755, true);
        }
        $cacheFile = $cacheDir . md5($key) . '.cache';
        file_put_contents($cacheFile, $value);
    }
}

步骤 3.2:创建一个页面 share.php 来调用工具类

这个页面将获取当前 URL,调用 WeChatHelper 生成签名,并将这些变量传递给前端。

// share.php
<?php
// 引入工具类
require_once 'WeChatHelper.php';
// 你的微信公众号配置
$appId     = '你的AppID';
$appSecret = '你的AppSecret';
$helper = new WeChatHelper($appId, $appSecret);
// 获取当前页面的完整 URL,这是生成签名的关键!
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
// 生成签名
$jsConfig = $helper->generateSignature($url);
// 如果生成失败,可以输出错误信息
if (!$jsConfig) {
    die("无法生成微信 JS-SDK 签名,请检查 AppID 和 AppSecret 是否正确。");
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">微信网页分享示例</title>
    <!-- 引入微信 JS SDK -->
    <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
</head>
<body>
    <h1>这是一个微信网页分享测试页面</h1>
    <p>请用微信内置浏览器打开此页面,然后点击右上角菜单进行分享。</p>
    <script>
        // 将 PHP 生成的变量传递给 JS
        window.wxConfig = {
            debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
            appId: '<?php echo $jsConfig['appId']; ?>',
            timestamp: <?php echo $jsConfig['timestamp']; ?>,
            nonceStr: '<?php echo $jsConfig['nonceStr']; ?>',
            signature: '<?php echo $jsConfig['signature']; ?>',
            jsApiList: [
                'updateAppMessageShareData', // 自定义“分享给朋友”及“分享到QQ”按钮的分享内容
                'updateTimelineShareData'   // 自定义“分享到朋友圈”及“分享到QQ空间”按钮的分享内容
            ]
        };
        // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个异步操作,所以需要在ready函数中调用
        wx.ready(function () {
            // 1. 分享给朋友
            wx.updateAppMessageShareData({
                title: '这是一个超棒的分享标题!', // 分享标题
                desc: '这是分享的详细描述,快来点击看看吧!', // 分享描述
                link: '<?php echo $url; ?>', // 分享链接,该链接域名或路径必须与当前页面的JS接口安全域名一致
                imgUrl: 'https://www.yourdomain.com/images/share-icon.jpg', // 分享图标,必须是绝对链接
                success: function () {
                    // 设置成功
                    console.log('分享给朋友设置成功');
                }
            });
            // 2. 分享到朋友圈
            wx.updateTimelineShareData({
                title: '朋友圈分享标题!', // 分享标题
                link: '<?php echo $url; ?>', // 分享链接,该链接域名或路径必须与当前页面的JS接口安全域名一致
                imgUrl: 'https://www.yourdomain.com/images/share-icon.jpg', // 分享图标,必须是绝对链接
                success: function () {
                    // 设置成功
                    console.log('分享到朋友圈设置成功');
                }
            });
        });
        // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
        wx.error(function (res) {
            console.error('微信JS-SDK配置失败:', res);
            alert('微信JS-SDK配置失败,请刷新页面重试。');
        });
    </script>
</body>
</html>

前端页面集成:调用 JS 接口

这部分代码已经包含在 share.php<script> 标签中了,关键点:

  • 引入 JS SDK<script src="https://res.wx.qq.com/open/js/jweixin-1.6.js"></script>
  • 配置 wx.config:将 PHP 传来的 appId, timestamp, nonceStr, signature 填入。
  • wx.ready 回调中调用分享接口wx.updateAppMessageShareDatawx.updateTimelineShareData
  • link 参数:必须和你公众号后台配置的 JS 安全域名一致,并且是绝对路径,在 PHP 中通过 $_SERVER['REQUEST_URI'] 获取当前 URL 是最稳妥的方式。
  • imgUrl 参数:也必须是绝对路径的图片链接,建议使用 https 协议,并且图片尺寸建议为 300x300 像素或更大,以保证清晰度。

常见问题与最佳实践

  1. 签名错误(invalid signature)

    • 检查 URL:确保 generateSignature 函数中的 $url 参数是当前页面的完整 URL,包括 http://https:// 和所有查询参数,可以在 PHP 中 echo $url; 来检查。
    • 检查 JS 安全域名:确认公众号后台配置的域名是否正确,并且已经生效。
    • 检查缓存:确保 access_tokenjsapi_ticket 的缓存没有问题,可以手动删除缓存文件或清空 Redis 来测试。
    • 检查排序jsapi_ticket, nonceStr, timestamp, url 四个参数进行字典序排序时,顺序不能错。
    • 检查编码:确保所有参数都没有多余的空格或 BOM 头。
  2. 域名未配置

    php 微信网页分享接口开发平台
    (图片来源网络,侵删)
    • 错误提示:invalid domain
    • 解决方法:立即检查公众号后台的“JS接口安全域名”配置。
  3. AppSecret 泄露

    • 绝对不要appSecret 写在前端 JavaScript 或暴露在任何客户端可以访问的地方,所有涉及 appSecret 的操作都必须在服务器端完成。
  4. 使用专业的缓存

    • 示例中的 WeChatHelper 使用了简单的文件缓存,这在高并发或生产环境中效率低下且不可靠,强烈建议使用 RedisMemcached 来缓存 access_tokenjsapi_ticket,这样可以显著提高性能和稳定性。
  5. SPA (单页应用) 的处理

    • 对于 Vue, React 等单页应用,路由变化时 URL 会改变,但 wx.config 只在页面初始化时调用一次,这会导致签名 URL 与当前浏览的 URL 不匹配。
    • 解决方法:在路由变化时,重新向后端请求新的签名,并再次调用 wx.config,可以在路由的 afterEach 钩子中执行此操作。

至此,一个完整的基于 PHP 的微信网页分享接口开发平台就搭建完成了,核心在于理解后端生成签名、前端使用签名的流程,并细心处理每一个细节。