微信分享JSSDK朋友圈缩略图:从签名到避坑全解析

📅 发布时间:2026/10/13 5:09:16
微信分享JSSDK朋友圈缩略图:从签名到避坑全解析
简介面向PHP网站的微信分享JSSDK集成demo适用于帝国CMS、DedeCMS、WordPress等主流建站系统可让朋友圈及好友分享的文章自动携带自定义标题、简介和缩略图解决默认分享无描述、无配图的常见问题。压缩包共5个文件包含2个PHP文件——一个负责获取access_token并完成JS-SDK签名另一个用于配置分享参数1个官方jweixin-1.2.0.js脚本用于前端初始化另有2个txt说明文档分别介绍安装步骤与access_token的获取方式。整体仅8KB结构清晰几乎不占用存储。参照说明文档只需替换两处参数即可直接使用即使不熟悉微信JS-SDK的开发者也能快速接入。目前已有559人学习下载该demo覆盖从服务端签名到前端注入的完整链路能有效规避缩略图不显示、签名失效等常见坑点适合作为各类PHP项目分享功能的参考模板。代码风格简洁便于二次开发与迁移。1. 微信分享jssdk朋友圈带缩略图一张图背后的三件事做过营销页或内容站的人都有同感H5做完业务方第一句就问分享到朋友圈带不带图。纯文字的分享卡片在聊天窗口里毫无存在感点都懒得点。微信分享jssdk要解决的就是这件事——用微信官方JS-SDK把分享到朋友圈和发给好友两套卡片配置起来让链接带上标题、描述和一张缩略图。这套demo的核心价值在于后端签名逻辑做成PHP通用类前端配置写成公共JS片段帝国cms、dedecms、wordpress三套站点都能直接嵌不用针对每个CMS重写一遍。适合做H5活动页、文章类站点和产品落地页的从业者只要有一个已认证的公众号把appId和appSecret填进配置照着后面步骤走就能跑通。2. 微信JSSDK签名机制后端三段数据流先把token和ticket的关系拆清很多人卡在第一步就是被签名搞懵了。前端要调的wx.config里那四个参数——appId、timestamp、nonceStr、signature其中signature不是随手算一个hash就完事儿它依赖一条完整的数据链。这条链不弄清楚后面所有代码都是黑匣子。2.1 access_token、jsapi_ticket 与 signature各自管什么微信不允许前端直接拿公众号身份去调接口所以设计成了三段递进关系第一段用 appId appSecret 换 access_token。这个token有效期7200秒代表你的公众号身份能调所有高级接口。注意它不能直接给前端页面用一旦暴露在浏览器里攻击者可以拿它调用公众号后台敏感接口。第二段用 access_token 换 jsapi_ticket。有效期同样是7200秒。微信刻意不让开发者用access_token直接参与签名中间加一道隔离即使jsapi_ticket被截获攻击面也远小于access_token泄露。第三段把 jsapi_ticket、noncestr、timestamp、url 四个值按 ASCII 码字典序拼成一个长字符串做一次sha1得到signature。前端把signature连同appId、timestamp、nonceStr传给微信客户端微信服务器用同一套规则自己算一遍两边一致才放行。环节入参出参有效期获取access_tokenappId appSecretaccess_token7200秒获取jsapi_ticketaccess_tokenjsapi_ticket7200秒生成signatureticket noncestr timestamp urlsha1字符串单次请求这里最容易让人头晕的是url到底传哪个。微信要求的是当前页面完整地址不含#及其后面部分。也就是说前端location.href.split(#)[0]拿到什么后端就原样签什么。很多项目把url做了一次urlencode或者把域名换成内网地址再去签结果前端永远报invalid signature这种问题我排过不下十次。2.2 PHP签名类一份能直接跑的通用实现下面这段PHP类就是demo包的核心部分按拿token→拿ticket→拼签名三个方法拆开写方便你移植到任何框架里。?php class WxJssdk { private $appId ; // 公众号 appId private $appSecret ; // 公众号 appSecret private $cacheDir ./wx_cache/; // token 缓存目录 public function __construct($appId, $appSecret, $cacheDir ./wx_cache/) { $this-appId $appId; $this-appSecret $appSecret; $this-cacheDir $cacheDir; if (!is_dir($this-cacheDir)) { mkdir($this-cacheDir, 0777, true); } } // 1. 获取 access_token文件缓存避免每次请求都打微信接口 public function getAccessToken() { $file $this-cacheDir . access_token.json; if (file_exists($file)) { $data json_decode(file_get_contents($file), true); if ($data[expire_time] time()) { return $data[access_token]; } } $url https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappid{$this-appId}secret{$this-appSecret}; $res json_decode($this-httpGet($url), true); if (isset($res[access_token])) { $data [ access_token $res[access_token], expire_time time() $res[expires_in] - 200, // 提前200秒过期避开临界点 ]; file_put_contents($file, json_encode($data)); return $res[access_token]; } error_log([wx] getAccessToken failed: . json_encode($res)); return null; } // 2. 用 access_token 换 jsapi_ticket同样走文件缓存 public function getJsapiTicket() { $file $this-cacheDir . jsapi_ticket.json; if (file_exists($file)) { $data json_decode(file_get_contents($file), true); if ($data[expire_time] time()) { return $data[jsapi_ticket]; } } $accessToken $this-getAccessToken(); if (!$accessToken) { return null; } $url https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token{$accessToken}typejsapi; $res json_decode($this-httpGet($url), true); if (isset($res[ticket])) { $data [ jsapi_ticket $res[ticket], expire_time time() $res[expires_in] - 200, ]; file_put_contents($file, json_encode($data)); return $res[ticket]; } error_log([wx] getJsapiTicket failed: . json_encode($res)); return null; } // 3. 生成前端需要的签名包 public function getSignPackage($url) { $jsapiTicket $this-getJsapiTicket(); if (!$jsapiTicket) { return null; } $timestamp time(); $nonceStr $this-createNonceStr(); // 签名原串必须按 ASCII 字典序排序 $string jsapi_ticket{$jsapiTicket}noncestr{$nonceStr}timestamp{$timestamp}url{$url}; $signature sha1($string); return [ appId $this-appId, timestamp $timestamp, nonceStr $nonceStr, signature $signature, url $url, ]; } 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; } private function httpGet($url) { $ch curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 线上建议开启CA验证 curl_setopt($ch, CURLOPT_TIMEOUT, 10); $output curl_exec($ch); $err curl_error($ch); curl_close($ch); if ($err) { error_log([wx] curl error: . $err); return ; } return $output; } }代码逻辑说明缓存读写都用文件因为帝国cms、dedecms这类PHP主机不一定给你开redis。缓存过期时间都减去200秒避免在临界点拿到即将失效的token。httpGet里curl关闭了SSL验证本地调试方便线上如果有CA证书建议改为true对签名逻辑没有影响但能少一次被中间人截获的风险。调用方式也很直接在某个控制器或者模板入口里$jssdk new WxJssdk($appId, $appSecret); $signPackage $jssdk-getSignPackage($currentUrl); // 把 $signPackage 传给模板输出成JS变量$currentUrl必须是前端页面实际的URL这个我在第4章会展开讲它值得单独占一条避坑。2.3 文件缓存的两道坎并发覆盖和服务器时间第一道坎是并发。两个用户同一秒触发分享各自调用getAccessToken发现缓存文件不存在同时去微信刷新token。后写的那份会把先写的覆盖掉而先写这个token对应的请求在微信那边可能已经被判定失效。后续请求用新token没问题但正在进行的旧token请求就会收到40001错误。解决办法是给缓存读写加锁打开文件时用flock加独占锁先到者刷新token后到者等待锁释放后直接读新缓存。第二道坎是服务器时间。签名里的timestamp用的是PHP的time()如果服务器时间比真实时间快几分钟微信在比对signature时会认为请求超时。排查这种问题很玄学——代码完全没动昨天还好好的今天就全部invalid signature。我一般先执行date看一眼时间偏差大的直接用ntp同步一次症状立刻消失。3. 分享配置与缩略图规则两套接口一套数据图的合规比title重要签名通了只是第一步接下来是前端配置。很多人以为分享带缩略图就是把一个img塞进页面里微信会自动抓取实际上必须通过JS-SDK主动告诉微信客户端分享时用这张图、这个标题这一步漏了页面里图片再多也白搭。3.1 前端挂载代码朋友圈和好友看到的不是同一份参数先把前端这段JS挂到页面里。我习惯把签名包用PHP直接输出成JSON变量再引微信官方脚本这样不用额外发Ajax请求。script src//res.wx.qq.com/open/js/jweixin-1.6.0.js/script script var sign ?php echo json_encode($signPackage); ?; wx.config({ debug: false, // 排障时可临时改成true上线记得关掉 appId: sign.appId, timestamp: sign.timestamp, nonceStr: sign.nonceStr, signature: sign.signature, jsApiList: [updateAppMessageShareData, updateTimelineShareData] }); wx.ready(function () { var shareData { title: 页面标题或自定义运营标题, desc: 分享给好友/群聊时展示的描述文案, link: location.href.split(#)[0], // 去掉#锚点 imgUrl: https://你的域名/path/thumb.jpg // 必须是完整URL }; // 配置朋友圈分享 wx.updateTimelineShareData(shareData); // 配置好友/群聊分享 wx.updateAppMessageShareData(shareData); }); /script这段代码有两个细节容易忽略第一updateTimelineShareData朋友圈只有title、link、imgUrl三个参数没有desc朋友圈卡片不展示描述文字。updateAppMessageShareData好友/群聊才有desc这也是为什么很多人发现聊天气泡有描述、朋友圈没有的原因不是代码写错了是微信压根不给朋友圈展示描述。第二这两个接口必须在wx.ready回调里调用而且每次分享前都应重新执行一遍。微信按会话缓存卡片配置同一页面换了内容后不重新覆盖再分享还是旧数据。3.2 缩略图的规矩尺寸、格式和URL完整性微信对缩略图有自己的一套裁图逻辑不摸清规矩图配了也白配尺寸上官方建议300x300以上但我实际测试下来最好用600x600的方形图。朋友圈卡片会把图裁成正方形横图或竖图都会被从中间截取主体元素不在正中间就会被切掉一半。格式用JPG或PNG别用GIF动图。动态图在卡片里不会动还平白多占带宽。体积控制在100KB以内微信压缩算法对大图不友好超过200KB的图偶发加载失败分享出去直接灰屏。imgUrl必须是外部可访问的完整URL带协议和域名相对路径一律不行。还有一点很多人不知道图片域名要和配置JS接口安全域名的域名一致不一致时签名能过但卡片拉图会被拒。3.3 帝国cms、dedecms、wordpress三套接入本质是三个变量的来源不同不用把问题想复杂。不管什么CMS前端需要的数据只有三个标题、描述、缩略图地址。签名逻辑是同一个PHP类接入工作就是把这三个变量从CMS里捞出来拼进页面。CMS标题变量来源描述变量来源缩略图变量来源嵌入位置帝国cms栏目/内容标题字段摘要字段标题图片字段头部模板直接调PHP标签dedecms文档标题summary字段litpic字段用{dede:field.*/}标签拼URLwordpressthe_title()the_excerpt()the_post_thumbnail_url()header.php或自定义页面模板帝国cms这边我一般是在头部模板里写一段PHP标签读取当前内容的标题和标题图片字段拼成完整URL赋给前面的shareData。dedecms同理模板里用{dede:field.litpic/}拿缩略图用{dede:global.cfg_basehost/}拼域名。wordpress最简单the_post_thumbnail_url()直接返回特色图片的完整URL主题的header.php里加一段PHP输出即可。4. 微信分享常见问题避坑自查五个高频翻车点这套东西踩坑点很集中基本就是签名、缓存、编码三类。我把实际排过的坑归纳成五条每条按现象→原因→解决写清楚上线前对着过一遍能省很多事。4.1 invalid signature十个里有八个是URL不一致现象wx.error回调提示invalid signature签名总是过不去后端怎么算都看着没问题。原因签名用的URL和页面实际URL不一致。常见三种情况一是页面带了#锚点没去掉二是接口做了rewrite或站内跳转前端拿到的URL与后端签名URL不是同一个域名三是URL里有中文或特殊字符前后端编码处理不一致。解决前端统一用location.href.split(#)[0]取URL传后端后端拿到后保持原样拼签名串不要额外做urldecode或urlencode。如果还报错把实际拼接的签名串打出来放到微信公众平台的签名校验工具里对比一下马上能看出是哪段参数变了。4.2 缩略图不显示先别怀疑代码查图片服务现象签名通过、分享面板能正常弹出来但卡片没有缩略图只有灰底。原因挨个排查大部分是这四种——imgUrl写成相对路径、图片被防盗链拦截、图片格式是GIF或WebP不支持、图片分辨率低于微信最小识别尺寸。解决浏览器直接打开imgUrl看能不能显示能显示再看格式和尺寸。换成300x300以上的JPG使用完整协议域名URL再确认公众号后台的JS接口安全域名已配置且与当前域名一致。4.3 access_token被并发刷新打挂缓存要加锁现象晚间流量高峰分享突然大面积报invalid credential错误码指向access_token。原因文件缓存没加锁两个请求同时发现缓存过期同时去微信刷新token后写覆盖先写被覆盖的token在微信侧即刻失效。解决读写缓存文件时用flock加独占锁先到者刷新、后到者等待锁释放后读新缓存。条件允许的话直接换redis的SETNX做原子操作彻底根除并发覆盖问题。4.4 老版本微信不执行新接口新旧API一起注册现象某些老版本安卓手机上分享出去没有自定义标题和缩略图。原因updateAppMessageShareData和updateTimelineShareData是微信6.6.6及以上版本才支持的接口低于这个版本的前端环境调用这两个方法不会生效。解决在wx.ready里同时注册旧版接口onMenuShareTimeline和onMenuShareAppMessage新版微信走新方法老版微信走旧方法。注册前用typeof做存在性判断避免老环境直接抛脚本错误。4.5 GBK模板里嵌UTF-8代码乱码编码声明别省现象帝国cms、dedecms这类默认GBK的站点嵌入demo里的UTF-8代码后中文字段全变乱码。原因PHP文件保存编码和模板输出编码不一致导致同一页面里GBK与UTF-8两套字节流共存。解决demo本身统一UTF-8接入时在模板head显式加meta charsetutf-8且只保留一处。如果CMS后台强制以GBK输出把PHP文件里的中文字符串转成\uXXXX形式或者用iconv做一次编码转换别复用GBK环境下同名变量。5. 验证分享效果的三种方式上线前必过的自查清单5.1 微信开发者工具先过一遍微信开发者工具打开页面后把wx.config的debug临时设为true控制台会直接显示config是ok还是报错。这里能看到签名相关的明确报错比手机端黑盒调试快得多。开发者工具过了至少能确认签名协议层没问题。5.2 真机验证必须做一次开发者工具正常不代表真机没问题尤其是图片加载和缓存这两部分。用手机微信打开页面观察弹出的debug信息显示config:ok说明签名通过若显示config:invalid signature回到第4章第1条对照排查。看完记得把debug改回false线上开着debug会弹窗干扰用户。5.3 顺手算一遍signature确认参数拼接// 四个变量按字典序拼好再sha1与后端返回的signature对比 $str jsapi_ticket{$ticket}noncestr{$nonce}timestamp{$ts}url{$url}; echo sha1($str);这条命令适合在本地环境快速核对签名串不用翻日志也能确认到底是URL变了还是ticket过期。上线前我固定跑一套自查清单JS接口安全域名配了没有、签名URL和当前页面是否完全一致、imgUrl能否直接访问、图片是否300x300以上且是JPG、jsApiList是否包含两个update接口、新旧分享接口是否都注册了、服务器时间差是否在合理范围。这套流程是被一次真实事故逼出来的——某次上线前没查老版本接口兼容现场发现一批旧安卓机分享不带图从那以后我每次接微信分享都强制走一遍这套清单希望帮到你。本文还有配套的精品资源点击获取