做「炊想」小程序时遇到一个很迷惑的问题:在菜谱页点「分享」发给朋友,我自己这边看到的卡片好好的,有标题有封面;对方收到的却只有小程序名,封面是一块灰色占位。前后改了两版分享代码都没用,最后发现跟代码无关——是小程序后台开着一个「分享请求签名校验」。
这篇把现象、走过的弯路、真正的原因和两种解法记下来。
一、现象:同一张卡片,两边看到的不一样
![]()
具体是这样:
- 发出方:卡片标题是代码里写的「「麻辣香锅」这样搭成一桌饭」,封面是这道菜的成品图,一切正常。
- 接收方:标题变成了小程序名「炊想」,封面位置是灰底加一个小程序图标,像是图片没加载出来。
- 不是偶发:昨天发的、今天发的都这样;换了分享代码、发了新版本以后还是这样。
- 点开卡片能正常进入小程序、打开那道菜,只是卡片长得不对。
二、先排除了哪些可能
一开始的判断是「分享信息没传对」或者「图片对方加载不出来」,顺着这个思路查了一圈:
onShareAppMessage没写,或者数据还没回来就分享了? 发出方自己的卡片标题、封面都对,说明代码交出去的分享内容没问题。- 封面图地址有问题(https、防盗链、过期)?
不带 Referer 直接请求图片地址,返回 200、
image/jpeg、约 50KB,图片是好的。而且接收方连标题都丢了,图片问题解释不了标题。 - 分享时先把图下成本地临时文件、再用
promise交给微信,对方拿不到? 2.0.11 改成同步返回、直接给 https 小图地址,接收方照旧,不是这里。 - 某一版改动引入的? 改动前发的卡片和改动后发的一样,不是版本问题。
排除到这里,有两条线索已经很明确了:
- 发出方正常、接收方异常:说明小程序把正确的数据交给了微信,问题出在微信「转交」给对方的环节。
- 标题和封面一起丢:对方看到的不是「图片挂了」,而是一张完整的默认卡片——默认标题就是小程序名,默认封面就是那个灰色占位。
也就是说,这张卡片被平台「降级」了。
三、真正的原因:分享安全校验
在微信官方文档里找到了《小程序分享签名》,说的正是这件事,要点是:
- 小程序后台有个「分享安全校验」选项。开启后,每次自定义分享都要带上开发者签名,平台会校验,验签失败的分享会被降级。
- 降级后的样子就是「默认分享标题 + 默认分享图」,和上面接收方看到的一模一样。
- 开启后,自定义分享必须指定
imageUrl,否则直接展示降级卡片。 - 签名对
appid、path、thumbDataHash(封面图的 SHA256)、title四个字段计算,算法是 RSAwithSHA256(PSS 填充)或 SM2withSM3,私钥由开发者自己保管,公钥或证书要先配置到平台。
回到小程序后台一看,这个开关确实是开着的:
![]()
位置在 小程序后台 → 管理 → 开发管理 → 开发设置 页面里的「安全管理配置」,提示文案是「已开启对该应用的分享请求签名校验」。我们的代码从来没有带过签名,所以每一次分享都被降级了。至于发出方为什么一直正常,我的理解是:发出方那张卡片是本机直接用分享数据画出来的,验签和降级只发生在下发给对方的环节——这也是这个坑最迷惑人的地方。
这个开关是什么时候打开的,已经无从考证了;它和开发者公钥、证书这些安全配置在一起,配置别的能力时顺手点开的可能性很大。
四、两种解法
解法一:不需要验签,就关掉它(我选的)
炊想的分享内容就是菜名和成品图,没有需要防篡改的敏感参数,所以直接点「关闭」:
![]()
关闭后不用改代码、不用发版,重新分享一次,对方收到的卡片标题和封面就都正常了。线上已经发布的旧版本也一样恢复。
解法二:需要验签,就把签名补上
如果你的分享链接里带了业务参数(邀请码、活动 id 之类),担心被人改了再转发,就应该保留校验、把签名做完整。关键是私钥只能放在服务端,所以流程大致是:
- 在小程序后台配置开发者公钥(或证书),私钥放服务器环境变量。
- 服务端提供一个签名接口:入参是
path、title、imageUrl;服务端下载封面图算出thumbDataHash(小写十六进制的 SHA256),按文档的规则拼签名串、用私钥签名,返回thumbDataHash和signature。 - 小程序在分享之前就把签名拿好(比如菜谱详情加载完、封面图就绪时请求一次缓存起来)。
onShareAppMessage最好同步返回;用promise的话微信最多等 3 秒,超时就用默认值,等于又降级了。 - 分享对象里多带两个字段:
onShareAppMessage() {
return {
title: this.share.title,
path: this.share.path,
imageUrl: this.share.imageUrl,
thumbDataHash: this.share.thumbDataHash, // 服务端算好的封面 SHA256
signature: this.share.signature // 服务端用私钥签的名
}
}
签名串的拼法按文档来:四个字段按 key 的字典序排列,value 先 UTF-8 再标准 Base64(不带换行),用 & 连成 appid=...&path=...&thumbDataHash=...&title=...,再用 RSA-SHA256 + PSS(salt 长度 32)签名后 Base64。几个容易错的点:
path要和分享对象里的一字不差,包括开头的/和 query 的编码方式。- 签的是
imageUrl下载下来的图片字节的哈希。图片地址如果带缩放参数(比如七牛的imageMogr2),要按带参数后实际返回的内容算。 - 用包内图片(
/assets/share-cover.jpg)当封面时,哈希按文档的说法应该就是这个文件本身的 SHA256,可以构建时算好(这一点我没有实测,做之前建议先用一张图验证)。 - PSS 签名每次结果都不一样,这是正常的,只能用公钥验,不能拿两次结果比对。
五、遇到类似现象,怎么快速判断
- 发出方正常、接收方的标题变成小程序名、封面是灰色占位:先别查代码,去后台看「安全管理配置」里的分享签名校验是不是开着。
- 发出方自己看到的卡片就已经不对:那才是代码问题,查
onShareAppMessage的返回值、异步数据、图片地址。 - 只有封面不显示、标题是对的:多半是图片本身的问题(地址不可访问、格式不是 png/jpg、太大太慢)。
六、顺手修掉的两处
排查过程中改过的分享代码,有两处是真的值得改,最后保留了下来:
分享图被压扁了:封面用的是七牛缩略参数
thumbnail/500x400!,这里的!表示「忽略原比例、强制缩放到这个尺寸」,1:1 的成品图被硬压成 5:4。改成先按比例铺满、再居中裁切:?imageMogr2/thumbnail/!500x400r/gravity/Center/crop/500x400/format/jpg/quality/80分享时不再临时下载图片:之前在分享那一刻先
downloadFile成本地临时文件,再用promise交给微信,链路长、还受 3 秒上限约束。现在直接同步返回 500×400、约 50KB 的 https 小图地址,成品图还没出来时用包内的分享图兜底。
七、几点体会
- 先分清「谁看到的」。这次如果一开始就注意到「发出方看到的是本地预览、接收方看到的才是平台下发的」,能少走很多弯路:本地正常而对方异常,问题多半在平台环节,而不是代码。
- 「标题和图片一起丢」和「只丢图片」是两回事。前者是整张卡片被换成了默认值,后者才是资源加载问题。
- 平台开关会改变行为,但不会出现在代码里。小程序后台的安全类开关(分享验签、API 安全、消息推送加密等)开了以后,最好在项目文档里记一笔:开了什么、为什么开、代码要配合做什么。
- 文档里的关键词是「降级」。以后遇到「平台把我的内容换成了默认样式」这类现象,可以直接拿「降级」去搜。

评论区 0