- 理解微信网页分享的原理
- 准备工作:获取必要的凭证
- 后端 PHP 开发:生成签名
- 前端页面集成:调用 JS 接口
- 常见问题与最佳实践
理解微信网页分享的原理
微信的 JSSDK (JavaScript SDK) 并不能直接让你自定义分享标题、描述和图片,它的核心流程是:

(图片来源网络,侵删)
- 你的服务器向微信服务器请求权限:你的 PHP 后端需要向微信服务器发送请求,获取一个临时的
access_token。 - 生成签名:使用
access_token、jsapi_ticket(另一个需要通过access_token获取的临时凭证)、URL(当前页面的完整URL)等信息,按照微信指定的算法生成一个signature签名。 - 后端将签名等参数传递给前端:你的 PHP 页面将
appId,timestamp,nonceStr,signature这四个关键参数传递给前端的 HTML/JavaScript 代码。 - 前端调用微信 JS 接口:前端 JavaScript 使用
wx.config和wx.ready方法来配置和调用微信的分享接口(如updateAppMessageShareData)。 - 用户分享:当用户在微信内置浏览器中点击右上角的“...”菜单,选择“分享给朋友”或“分享到朋友圈”时,就会把你设置好的标题、描述、图片和链接分享出去。
关键点:所有敏感操作(如获取 access_token 和 jsapi_ticket)都在你自己的服务器(PHP)上完成,只将安全的签名参数传递给前端,这保证了你的 AppSecret 等核心密钥不会泄露。
准备工作:获取必要的凭证
在开始编码之前,你必须拥有以下信息:
- 一个已认证的微信公众号:必须是服务号(订阅号没有网页分享权限),并且已开通“网页授权”和“JS接口安全域名”。
- AppID (应用ID):在公众号后台“开发者中心”可以找到。
- AppSecret (应用密钥):同样在“开发者中心”可以找到,这个非常重要,绝不能泄露!
- 设置 JS接口安全域名:
- 登录微信公众平台。
- 进入“设置与开发” -> “公众号设置” -> “功能设置”。
- 在“JS接口安全域名”一栏,添加你的网站域名(
www.yourdomain.com)。 - 注意:域名需要通过备案,且添加后需要一段时间(几分钟到几小时)才能生效,
http请求会自动跳转为https,所以确保你的服务器支持 SSL。
后端 PHP 开发:生成签名
这是整个开发的核心,我们需要创建一个 PHP 类来管理 access_token 和 jsapi_ticket,并提供生成签名的方法。
步骤 3.1:创建一个工具类 WeChatHelper.php
这个类将负责:

(图片来源网络,侵删)
- 缓存
access_token和jsapi_ticket(因为它们是有时效性的,避免频繁请求微信服务器)。 - 从微信服务器获取新的
access_token和jsapi_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}×tamp={$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.updateAppMessageShareData和wx.updateTimelineShareData。 link参数:必须和你公众号后台配置的 JS 安全域名一致,并且是绝对路径,在 PHP 中通过$_SERVER['REQUEST_URI']获取当前 URL 是最稳妥的方式。imgUrl参数:也必须是绝对路径的图片链接,建议使用https协议,并且图片尺寸建议为 300x300 像素或更大,以保证清晰度。
常见问题与最佳实践
-
签名错误(invalid signature)
- 检查 URL:确保
generateSignature函数中的$url参数是当前页面的完整 URL,包括http://或https://和所有查询参数,可以在 PHP 中echo $url;来检查。 - 检查 JS 安全域名:确认公众号后台配置的域名是否正确,并且已经生效。
- 检查缓存:确保
access_token和jsapi_ticket的缓存没有问题,可以手动删除缓存文件或清空 Redis 来测试。 - 检查排序:
jsapi_ticket,nonceStr,timestamp,url四个参数进行字典序排序时,顺序不能错。 - 检查编码:确保所有参数都没有多余的空格或 BOM 头。
- 检查 URL:确保
-
域名未配置
(图片来源网络,侵删)- 错误提示:
invalid domain。 - 解决方法:立即检查公众号后台的“JS接口安全域名”配置。
- 错误提示:
-
AppSecret 泄露
- 绝对不要将
appSecret写在前端 JavaScript 或暴露在任何客户端可以访问的地方,所有涉及appSecret的操作都必须在服务器端完成。
- 绝对不要将
-
使用专业的缓存
- 示例中的
WeChatHelper使用了简单的文件缓存,这在高并发或生产环境中效率低下且不可靠,强烈建议使用 Redis 或 Memcached 来缓存access_token和jsapi_ticket,这样可以显著提高性能和稳定性。
- 示例中的
-
SPA (单页应用) 的处理
- 对于 Vue, React 等单页应用,路由变化时 URL 会改变,但
wx.config只在页面初始化时调用一次,这会导致签名 URL 与当前浏览的 URL 不匹配。 - 解决方法:在路由变化时,重新向后端请求新的签名,并再次调用
wx.config,可以在路由的afterEach钩子中执行此操作。
- 对于 Vue, React 等单页应用,路由变化时 URL 会改变,但
至此,一个完整的基于 PHP 的微信网页分享接口开发平台就搭建完成了,核心在于理解后端生成签名、前端使用签名的流程,并细心处理每一个细节。
