简介:这份源码面向使用帝国CMS 7.5 UTF8版搭建内容站点的站长与开发者,用于解决微信内分享文章时朋友圈和转发好友不显示缩略图、简介缺失的问题。压缩包共6个文件,以php脚本、txt安装说明、js前端脚本和jpg示意图为主,整体约31KB,结构轻量,便于对照部署与快速查阅。已有1040人学习下载,适合需要为站点补齐微信分享卡片的运营与技术人员参考。内容围绕公众号认证、域名备案、appid与appsecret配置、JS接口安全域名及IP白名单等关键环节给出示例与说明,并附带前端微信JS接入文件,可帮助读者理解分享缩略图和摘要的生成逻辑。随包说明还整理了获取access_token的验证思路和常见配置错误排查方向,便于在调试分享效果时定位问题。适合作为帝国CMS站点微信分享功能落地的参考源码与排错资料。
1. 从一条「裸链接」说起:微信分享缩略图到底缺了哪一环
做帝国CMS站点的同行多半碰到过这个场面:一篇文章在群里转发出去,微信卡片上只有一行灰扑扑的标题加个域名,缩略图没有、简介没有,点开率低得让人怀疑内容质量。而隔壁同行的文章一转就是大图加两行摘要,视觉上先赢一截。很多人第一反应是去改页面的og:image、<meta name="description">,改完发现微信压根不看这些标签。微信分享卡片的标题、简介、缩略图,靠的是页面主动调用微信 JS-SDK 把数据传给客户端,注册过了才会渲染,不注册就退回默认的裸链接样式。这份源码包解决的就是这一环:给帝国CMS 的文章详情页接上微信 JS-SDK,让转发好友和分享到朋友圈都带上缩略图和简介。它适合手上跑着帝国CMS、内容主要靠微信传播的站长和二次开发者,也适合想搞明白 JSSDK 签名链路到底怎么走的人。
2. 拆开这个源码包:目录结构、运行环境与分享链路全景
2.1 包里到底有什么:文件清单与各自职责
拿到包先别急着往服务器上扔,把目录铺开看一眼,心里就有数了。这类分享组件的组织方式大同小异,一个服务端签名模块加一个前端注入脚本,剩下的就是模板片段和说明文件。常见的目录结构长下面这样:
| 路径 | 作用 | 到手要不要改 |
|---|---|---|
wxshare/config.php | 存放 AppID、AppSecret、缓存目录、调试开关 | 必须改 |
wxshare/jssdk.php | 核心类:取 access_token、取 jsapi_ticket、算签名 | 一般不动 |
wxshare/signature.php | 前端 AJAX 调用的签名接口 | 域名白名单要改 |
wxshare/share.js | 页面里执行wx.config和注册分享数据的脚本 | 按需微调 |
wxshare/cache/ | 存 token 和 ticket 的 JSON 缓存文件 | 要保证可写 |
template/share_snippet.html | 塞进帝国CMS 内容模板的片段 | 按模板变量对接 |
readme.txt | 配置步骤和常见错误说明 | 看一遍 |
服务端三个文件是一条线:config.php提供参数,jssdk.php拿着参数去换凭证并算签名,signature.php把结果以 JSON 吐给浏览器。前端两个文件是一条线:模板片段把当前文章的标题、简介、封面图写进一个全局变量,share.js再去请求签名、配置wx.config、注册分享接口。中间那块cache/目录经常被忽略,但它是整个链路里最容易翻车的地方——目录权限不对,token 每次都重新拉,接口就会被限流,表现就是分享时好时坏。
我把这套结构叫「两段式」:服务端只管签名,不碰业务数据;前端只管取数,不碰密钥。好处是 AppSecret 永远不出现在 HTML 里,坏处是多一次 HTTP 往返,首屏分享数据会晚个几十毫秒。对文章页来说这点延迟无所谓,因为用户点右上角之前页面早就加载完了。
2.2 一次转发从点击到卡片的完整链路
理解链路比背配置项重要得多。用户在微信里打开文章页,浏览器开始加载,模板片段先把这篇文章的title、smalltext、titlepic渲染成window.__SHARE__对象;接着share.js执行,拿location.href.split('#')[0]作为待签名 URL,带着它去请求signature.php;服务端校验 Referer 和 URL 域名,读缓存里的jsapi_ticket,按字典序拼串做 sha1,返回appId / timestamp / nonceStr / signature四件套;前端拿到后调wx.config,微信客户端校验签名,通过后触发wx.ready;在wx.ready里注册updateAppMessageShareData(转发给好友)和updateTimelineShareData(分享到朋友圈);用户点右上角菜单时,微信读的正是这份注册数据,卡片于是有了图有了文。
这里有个反直觉的点值得单独说:微信客户端不爬你的页面。它不会去解析你的<meta property="og:image">,也不会去抓正文第一张图当缩略图。分享卡片上的所有信息,都来自页面里 JS 主动注册的那份数据。所以改og标签这件事在微信生态里基本是白干,很多人在这上面耗掉一个下午才发现方向错了,血泪经验。
链路里还有两个容易断的环节。一是签名 URL 必须和当前页面 URL 完全一致,协议、域名、端口、路径、查询参数一个字符都不能差,差一个www就是invalid signature。二是wx.config一个页面只能成功执行一次,重复调用或者被别的脚本抢先调了,后面的分享注册就全废。这两个点在后面的排查章节还会展开。
2.3 跑起来之前必须备齐的四样东西
代码写得再对,前置条件没备齐也是白搭。这四样东西缺一个,页面上的表现都是「卡片没变化」,很难从报错反推,所以最好一次配齐。
第一是公众号的接口权限。订阅号和服务号在认证之后才有 JSSDK 能力,未认证的号调jsapi_ticket会直接返回错误码。第二是后台的「JS 接口安全域名」配置,位置在公众号设置的功能设置里,填的是纯域名,不带http://,不带路径,填完要下载一个校验文件放到网站根目录,能被https://你的域名/MP_verify_xxxx.txt直接访问到。第三是「IP 白名单」,位置在开发配置里,填服务器的出口 IP——注意是出口 IP,不是内网 IP,多台机器负载均衡的话每台都要填。第四是服务器要能出网访问微信接口,有些机房默认封了外发 443,得单独确认。
第三和第四点可以用一条命令先验一遍,省得后面在代码里猜:
# 把 ACCESS_TOKEN 换成你手动换到的,能返回 errcode:0 就说明白名单和出网都通了 curl -s "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi" # 服务器出网自检,能拿到 IP 就说明 443 没被封 curl -s https://api.weixin.qq.com/cgi-bin/getcallbackip第一条命令返回{"errcode":0,"errmsg":"ok","ticket":"...","expires_in":7200}说明白名单没问题;返回40164就是 IP 没加白名单;返回40001是 token 不对。第二条能返回 JSON 说明出网通。这两步做完再动代码,能少走一半弯路。
3. 服务端核心:token、ticket 与签名接口逐段拆
3.1 缓存层:别让 access_token 被刷爆
access_token是公众号的全局凭证,有效期 7200 秒,而且有个特性:新 token 签发后,旧 token 大约 5 分钟后失效。如果多台机器、多个 PHP 进程同时去刷新,就会出现你刷我、我刷你的互踢,表现是接口时不时报40001 invalid credential,分享时好时坏,特别像玄学。解决办法不复杂,加一把文件锁,让「读缓存 — 过期判断 — 远程拉取 — 写回缓存」这四步串行执行。
<?php // wxshare/jssdk.php 片段:带文件锁的缓存读写 private function fetchWithLock($key, callable $fetcher, $ttl) { $file = $this->cacheDir . '/' . $key . '.json'; $fp = fopen($file, 'c+'); // c+ 模式:不存在就创建,指针在开头 if (!$fp) { throw new Exception("缓存文件不可写: {$file}"); } if (!flock($fp, LOCK_EX)) { // 排它锁,并发请求在这里排队 fclose($fp); throw new Exception('缓存加锁失败'); } $raw = stream_get_contents($fp); $data = $raw ? json_decode($raw, true) : null; // 提前 60 秒判定过期,别卡在临界点上用到一个即将失效的凭证 if ($data && isset($data['expire_at']) && $data['expire_at'] > time() + 60) { flock($fp, LOCK_UN); fclose($fp); return $data['value']; } $value = $fetcher(); // 锁内远程拉取,其它请求阻塞等待 ftruncate($fp, 0); // 清空旧内容再写,避免残留字符 rewind($fp); fwrite($fp, json_encode([ 'value' => $value, 'expire_at' => time() + $ttl, ])); fflush($fp); flock($fp, LOCK_UN); fclose($fp); return $value; }这段的关键在于锁的位置。锁必须包住「判断过期」和「远程拉取」两步,只在写文件时加锁是没用的,因为两个进程可以同时判断为过期,然后各自去拉一次。ftruncate那行也别省,c+模式下文件指针在开头但内容还在,直接fwrite短内容会留下尾巴,json_decode就会失败。$ttl传微信返回的expires_in而不是硬编码 7200,遇到接口临时调整也不会出问题。
我一般会把expire_at判断往前挪 60 秒,这个余量看起来小,实际能挡掉一批临界失效的报错。缓存目录建议放在网站根目录之外,或者至少加个.htaccess拒绝直接访问,别让人把 token 文件下载走。
3.2 签名算法:字典序拼接与 sha1 的坑
拿到jsapi_ticket之后就是拼串。微信的规则写得很死:把jsapi_ticket、noncestr、timestamp、url四个参数按参数名 ASCII 码从小到大排序,用key=value&的形式拼成一个字符串,字段名和值都做 URL 解码,最后对这个字符串做 sha1,得到的就是signature。注意是 sha1,不是 md5,也不是 sha256,这个在几十份抄来抄去的博客里被写错过的次数不少。
<?php // wxshare/jssdk.php 片段:生成签名包 public function getSignPackage($url) { $ticket = $this->getJsapiTicket(); $nonceStr = substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, 16); $timestamp = time(); $url = strtok($url, '#'); // 微信明确要求:不含 # 及其后面部分 $params = [ 'jsapi_ticket' => $ticket, 'noncestr' => $nonceStr, 'timestamp' => $timestamp, 'url' => $url, ]; ksort($params); // 按 key 的 ASCII 码升序 // http_build_query 会对值做 urlencode,这里必须转回来, // 因为微信要求的是「解码后的值」参与拼接 $string1 = urldecode(http_build_query($params)); $signature = sha1($string1); return [ 'appId' => $this->appid, 'timestamp' => $timestamp, 'nonceStr' => $nonceStr, 'signature' => $signature, 'url' => $url, // 回传给前端,方便出错时对比 ]; }参数说明一下。$url是要签名的页面地址,由前端传进来,服务端只做#截断,不做别的清洗——任何重写都可能导致前后端不一致。$nonceStr用 16 位随机串,长度没有硬性要求,但别用固定值,微信会认为是重放。ksort之后数组变成jsapi_ticket → noncestr → timestamp → url的顺序,恰好是 ASCII 升序。http_build_query那一行是整个算法的坑点:它默认会把url里的:和/编码成%3A、%2F,而微信要求的是原始值,所以得urldecode回来。这一进一出看着多余,实际是微信官方 demo 的写法,照抄最稳。
返回值里我额外带了url,不是微信要求的,纯粹是为了排查。前端可以拿它和location.href.split('#')[0]对比,一眼就能看出是不是 URL 不一致导致的签名失败。
3.3 对外接口:把签名结果吐给前端
signature.php是浏览器唯一能碰到的服务端文件,它有两个职责:生成签名,以及别让自己变成一个公开的签名服务。第二点经常被忽略——如果任何域名都能拿你的接口签任意 URL,别人就可以用你的公众号身份去伪造分享卡片,标题图片随便写,出了事算在你头上。
<?php // wxshare/signature.php header('Content-Type: application/json; charset=utf-8'); $conf = require __DIR__ . '/config.php'; require __DIR__ . '/jssdk.php'; $allow = ['www.example.com', 'example.com']; // 换成你自己的域名,别用通配 $referer = $_SERVER['HTTP_REFERER'] ?? ''; $refHost = parse_url($referer, PHP_URL_HOST); $url = $_GET['url'] ?? ''; $urlHost = parse_url($url, PHP_URL_HOST); // 双重校验:来源页面和待签 URL 都必须在白名单里 if (!in_array($refHost, $allow, true) || !in_array($urlHost, $allow, true)) { http_response_code(403); echo json_encode(['code' => 403, 'msg' => 'domain not allowed']); exit; } try { $sdk = new WxJssdk($conf); $data = $sdk->getSignPackage($url); echo json_encode(['code' => 0, 'data' => $data], JSON_UNESCAPED_SLASHES); } catch (Exception $e) { // 生产环境别把异常原文吐给前端,写日志即可 error_log('[wxshare] ' . $e->getMessage()); echo json_encode(['code' => 500, 'msg' => 'sign failed']); }HTTP_REFERER可以被伪造,所以它只是第一道门槛,真正起作用的是$url的域名校验。JSON_UNESCAPED_SLASHES让返回的 URL 保持可读,不转义成\/,调试时省事。异常处理里把详细信息写进error_log,前端只拿一个笼统的 500,避免把 AppSecret 相关的错误细节暴露出去。
接口的响应时间要盯一下。正常命中缓存的话,这个接口应该在 10 毫秒以内返回;如果稳定在几百毫秒,八成是缓存没生效,每次都在走远程请求。可以在返回值里加个cached字段标记是否命中,压测时一眼就能看出来。
4. 前端与模板层:把 wx.config 塞进详情页
4.1 模板里怎么拿标题、简介、封面图
帝国CMS 的内容模板里,当前文章的数据放在$navinfor数组里,title是标题,smalltext是简介,titlepic是封面图,newstext是正文,classid是所属栏目。模板是编译成 PHP 执行的,所以可以直接写 PHP 片段。常见的做法是在模板底部插一段,把三个值算好之后用json_encode输出到全局变量里,避免中文和引号把 JS 语法搞崩。
<?php // 内容模板片段:计算分享用的标题、简介、缩略图 $shareTitle = $navinfor['title']; $shareDesc = trim($navinfor['smalltext']); if ($shareDesc === '') { // 简介没填就从正文截,先剥 HTML 再去空白,最后按字符截断 $shareDesc = strip_tags($navinfor['newstext']); $shareDesc = preg_replace('/\s+/u', '', $shareDesc); $shareDesc = mb_substr($shareDesc, 0, 50, 'UTF-8'); } $shareImg = trim($navinfor['titlepic']); if ($shareImg === '') { // 文章没封面就取栏目图,栏目表 classimg 字段 $cls = $empire->fetch1("select classimg from {$dbtbpre}enewsclass where classid='" . (int)$navinfor['classid'] . "'"); $shareImg = $cls['classimg'] ?? ''; } if ($shareImg === '') { $shareImg = '/static/img/share-default.jpg'; // 最后兜底 } // 相对路径补成绝对路径,微信不认相对地址 if (strpos($shareImg, 'http') !== 0) { $shareImg = 'https://www.example.com' . $shareImg; } ?> <script> window.__SHARE__ = { title: <?= json_encode($shareTitle, JSON_UNESCAPED_UNICODE) ?>, desc: <?= json_encode($shareDesc, JSON_UNESCAPED_UNICODE) ?>, imgUrl: <?= json_encode($shareImg, JSON_UNESCAPED_SLASHES) ?> }; </script> <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> <script src="/wxshare/share.js"></script>几个细节值得停一下。$empire和$dbtbpre是帝国CMS 的全局对象和表前缀,内容模板里可以直接用,但要注意$dbtbpre拼进 SQL 时不要加引号。json_encode必须带JSON_UNESCAPED_UNICODE,否则中文会变成\u4e2d\u6587这种形式,虽然 JS 能解析,但调试时看着费劲,而且某些老版本的微信客户端对转义后的中文处理有偏差。mb_substr的第四个参数显式写UTF-8,别依赖mb_internal_encoding,服务器环境不一样结果会不一样。
简介截断到 50 字是我的习惯。微信好友分享的摘要区域大概能显示两行多,超过之后会被省略号截掉,与其让它乱截,不如自己控制好断点。朋友圈那条更狠,desc根本不显示,后面会说。
4.2 jweixin.js 的配置与两个分享接口
前端这块的核心就三件事:请求签名、wx.config、在wx.ready里注册分享数据。顺序不能乱,位置也不能错——分享接口必须写在wx.ready回调里面,写在wx.config后面直接调用是不生效的,因为此时配置还没校验通过。
// wxshare/share.js (function () { var share = window.__SHARE__ || {}; function initWx(cfg) { wx.config({ debug: false, // 排查时改 true,会在页面上弹出校验结果 appId: cfg.appId, timestamp: cfg.timestamp, nonceStr: cfg.nonceStr, signature: cfg.signature, jsApiList: [ 'updateAppMessageShareData', 'updateTimelineShareData', 'onMenuShareTimeline', 'onMenuShareAppMessage' ] }); wx.ready(function () { var link = location.href.split('#')[0]; // 转发给好友:标题、简介、链接、缩略图都能设 wx.updateAppMessageShareData({ title: share.title || document.title, desc: share.desc || '', link: link, imgUrl: share.imgUrl || '', fail: function (e) { console.warn('[wxshare] 好友分享失败', e); } }); // 分享到朋友圈:只有 title / link / imgUrl 三个参数有效 wx.updateTimelineShareData({ title: share.title || document.title, link: link, imgUrl: share.imgUrl || '' }); // 老客户端兜底:新接口在低版本微信里可能不存在 if (wx.onMenuShareAppMessage) { wx.onMenuShareAppMessage({ title: share.title, desc: share.desc, link: link, imgUrl: share.imgUrl }); } if (wx.onMenuShareTimeline) { wx.onMenuShareTimeline({ title: share.title, link: link, imgUrl: share.imgUrl }); } }); wx.error(function (err) { console.warn('[wxshare] config 失败', err); }); } // 拉签名 var xhr = new XMLHttpRequest(); xhr.open('GET', '/wxshare/signature.php?url=' + encodeURIComponent(location.href.split('#')[0]), true); xhr.onload = function () { var res = {}; try { res = JSON.parse(xhr.responseText); } catch (e) { return; } if (res.code !== 0) { console.warn('[wxshare]', res.msg); return; } initWx(res.data); }; xhr.send(); })();新旧接口同时注册是有意为之。updateAppMessageShareData和updateTimelineShareData是后来的接口,覆盖绝大多数机型;onMenuShareAppMessage和onMenuShareTimeline是早期接口,在部分老版本客户端上仍然有效。两套都写,微信会优先用新的,老的当作保险。代价是代码长一点,换来的是兼容性,我觉得值。
link参数也有讲究。它决定用户点开卡片后跳到哪个页面,默认填当前页 URL 就行。但如果你的站点有 PC 端和移动端两套域名,这里最好显式指定移动端地址,否则用户在手机上点开可能被跳到 PC 版页面。
4.3 默认图与降级:没有封面图也不能空着
缩略图是分享卡片里最影响点击率的部分。微信对图片有几个隐性要求:必须是绝对地址、格式最好是 jpg 或 png、尺寸建议 300×300 以上(500×500 更稳)、文件别太大。如果imgUrl为空或者加载失败,卡片会退化成没有图的样式,跟没配一样。
所以降级链路要设计好:文章封面 → 栏目图 → 站点默认图。三级兜底基本能覆盖所有情况。栏目图存在{$dbtbpre}enewsclass表的classimg字段里,在后台栏目管理里能设置。站点默认图就放一张设计好的通用图,500×500,白底带 logo 那种,别用透明 PNG,有些机型上透明区域会渲染成黑块。
还有一点容易忘:imgUrl的域名最好和 JS 安全域名一致。微信对跨域图片的抓取有限制,虽然不绝对,但同域最稳。如果你的图片放在对象存储上,记得把那个域名也加到安全域名列表里,或者干脆在服务端做一层转发。
5. 避坑与排查:分享卡片不生效的六种真实现象
5.1 签名与域名类:config 一直 fail
现象一:控制台报config:invalid signature,卡片完全没变化。
原因基本只有一个:签名用的 URL 和当前页面 URL 不一致。常见变体包括前端传了带#的完整地址、传了被encodeURIComponent处理过的地址、站点做了http到https的跳转导致协议不同、带www和不带www混用。解决方法是前端统一用location.href.split('#')[0],服务端用strtok($url, '#')再截一次,把debug打开,微信弹出的提示里会显示它认为的当前 URL,和你签名用的 URL 逐字符对比,包括大小写和末尾斜杠。
现象二:报config:invalid url domain,签名都没走到。
这是安全域名没配或者配错了。公众号后台的 JS 接口安全域名填的是纯域名,不要带协议和路径;填完之后要下载校验文件放到网站根目录,能被公网直接访问到。还有一种情况是填了www.example.com,但用户实际访问的是example.com,这两个在微信眼里是两个域,必须都配上,或者做 301 统一到一个。
现象三:config成功了,但分享还是灰的。
多半是分享接口没写在wx.ready里,或者页面里有两个脚本各自调了一次wx.config。微信规定一个页面只能成功config一次,后调的那次会覆盖前面的jsApiList,导致分享接口不在白名单里。解决方法是全局只保留一处config,把所有要用的接口名合并进同一个jsApiList数组。如果站点上还装了别的微信相关插件,先确认它们有没有偷偷调config。
5.2 内容与缓存类:卡片出来了但不对
现象四:转发给好友有图,分享到朋友圈没图没简介。
这不是 bug,是接口设计如此。updateTimelineShareData只接受title、link、imgUrl三个参数,desc传了也会被忽略。朋友圈卡片的样式就是「大图 + 标题」,没有摘要位。如果你希望朋友圈也能传达更多信息,只能把关键信息压缩进title里,比如「标题|栏目标签」这种写法,注意总长度别超过 30 个字,超了会被截。
现象五:第一次分享没图,刷新一次就有了。
典型的时序问题。iOS 上尤其明显:用户打开页面后立刻点右上角,此时wx.config还没走完,分享数据没注册上,微信就用默认样式渲染了。解决办法是把config尽量提前,别等图片加载完、别等 DOMContentLoaded,脚本放在模板底部紧跟数据变量之后就执行。另外debug关掉能省一点开销。如果站点是 SPA 路由,每次路由切换都要重新config一次,因为 URL 变了签名就失效了。
现象六:access_token隔一段时间就报40001,分享时好时坏。
并发刷新互踢。多个 PHP 进程同时发现 token 过期,一起去拉新的,微信签发新的之后旧的失效,先拿到旧的那批请求就报错了。解决就是 3.1 节那套文件锁,锁住「判断 + 拉取 + 写回」的完整过程。如果站点是多台服务器,文件锁只能锁单机,得换成 Redis 分布式锁,或者干脆起一个中控服务统一管理 token,其它机器通过内网接口取。还有个更省事的方案是使用微信的稳定版 token 接口,它对同一 AppID 的重复获取有更宽松的处理,适合多实例场景。
顺带提一个容易忽略的点:同一个 AppID 不要被多个系统共用。主站、小程序后端、第三方工具如果都拿同一个 AppID 去换 token,互踢是必然的,而且排查起来特别费劲,因为你不一定知道另一个系统什么时候在刷。
6. 二次开发:从「全站一套图」到「按栏目出图、按场景出文案」
把这套东西跑通只是起点,真正让它好用还得改两刀。第一刀是分享图的粒度。全站一张默认图,用户转发十篇文章看到的都是同一个缩略图,辨识度很低。我的做法是给每个栏目配一张 500×500 的视觉图,存在栏目表的classimg字段里,文章没封面就用栏目图兜底,这样至少能区分出「新闻」「教程」「案例」几个大类。再进一步可以按作者或者按关键词出图,但收益递减,不如把栏目图做好看。
第二刀是分享文案的场景化。文章标题往往偏正式,转发到群里不一定有人点。可以在内容模型里加一个「分享标题」字段,编辑填一句更口语的,没填就回落到原标题。同理加一个「分享语」,专门给好友分享用,长度控制在 40 字以内。这两个字段在帝国CMS 后台的「管理数据表」里加,加到主表就行,注意别加到副表,副表查询要多一次 join。
改完之后怎么验证?别靠肉眼一遍遍转发。我一般会加一个自检页,把签名接口的返回、当前页 URL、window.__SHARE__的内容全打出来,再用微信打开这个页面,对着debug: true的弹窗逐项核对。这个页面只在测试域名下开放,用 IP 或者内网限制访问。
// /wxshare/selfcheck.js —— 只在测试环境挂载 (function () { if (location.hostname !== 'test.example.com') return; var pageUrl = location.href.split('#')[0]; fetch('/wxshare/signature.php?url=' + encodeURIComponent(pageUrl)) .then(function (r) { return r.json(); }) .then(function (res) { console.table({ '当前页 URL': pageUrl, '签名用 URL': res.data && res.data.url, '是否一致': res.data && res.data.url === pageUrl ? '是' : '否,这里就是问题所在', '签名': res.data && res.data.signature }); }); })();这个自检页帮我省过好几次事。有一次线上突然大面积没图,一对比发现是 CDN 把http回源成了https,签名 URL 和实际 URL 协议不一致,改配置十分钟解决,要是靠猜可能得折腾一下午。
还有个小技巧:imgUrl支持带查询参数的地址,所以可以给缩略图加个版本号?v=202401,改图之后不用等 CDN 缓存过期。另外微信对图片有抓取超时,如果图片服务器响应慢,卡片会没图,这时候把图片放到和页面同域的静态目录下最稳。
从那以后我每次接微信 JSSDK,都强制先跑一遍「四样前置条件」清单,再打开debug: true用真机验证一次签名,最后才去调样式。顺序反了,后面全是无用功。希望帮到你。
本文还有配套的精品资源,点击获取