微信 JS-SDK 网页调用与签名避坑
你在本地调试一切正常,代码一上测试环境,分享出去的卡片就没有标题和图片;点「拍照」没反应;扫一扫报 config 失败。你先怀疑参数拼错,改了半天才发现真正的原因:签名的 url 用的是后端配置的域名,而不是当前页面地址。JS-SDK 的大多数问题都集中在这一环,接口不难调,难的是签名那串字符串必须和当前页面严格对齐,差一个字符就全部失效。
一、JS-SDK 能替你做什么
它给微信内置浏览器里的网页开了一扇窗,把原生能力用前端接口暴露出来。项目里真正用得上的是这几类。
- 自定义分享卡片:把只有链接的干瘪转发换成带标题、描述、缩略图的卡片,这是多数业务接入的唯一理由。
- 拍照与选图:直接调起相册与相机,返回图片本地 id,再配合上传接口拿服务端地址。
- 地理位置:拿到当前经纬度,或调起地图选点让用户手动挑一个位置。
- 扫一扫:扫二维码、条形码,常用于扫码核销、扫码登录、扫码取货。
- 微信支付:在页面内发起支付,不用跳出到独立收银台。
- 其他:隐藏右上角菜单项、图片预览、网络状态、卡券等。
前提是页面已经在微信客户端内打开。在桌面浏览器或手机自带浏览器里,整套接口都不存在,你必须为这条路径准备降级方案,否则用户点分享时页面会直接卡住。
二、接入前的三件前置事
公众号必须认证
未认证的公众号拿不到完整接口权限,分享类接口会直接返回权限不足。认证之后还要在后台配置「JS 接口安全域名」。
安全域名不是随便填的
三条硬规则:域名必须已备案;不带协议头也不带路径;对域名下所有子路径生效,但不跨子域名。改完通常要几分钟生效,别改完立刻测然后怀疑代码。
脚本要在业务代码之前加载
<script src="https://res.wx.qq.com/open/js/jweixin.js"></script>
脚本必须先于业务代码执行完成,否则 wx 是 undefined。用构建工具时别把它当本地依赖塞进 bundle,也别为了加载速度做延迟注入。
三、config 的七个参数
签名校验成功后,前端调 wx.config 注入权限,参数七个,缺一个或错一个都会失败。
| 参数 | 说明 |
|---|---|
| debug | 开发期设为 true,会弹出每步校验信息,上线必须关掉 |
| appId | 公众号 appid,必须与签名用的 ticket 属同一账号 |
| timestamp | 秒级时间戳,与参与签名的那个完全一致 |
| nonceStr | 随机字符串,同样要与签名里的一致 |
| signature | 服务端算出的 SHA1 结果 |
| jsApiList | 要用的接口清单,只写要用的,写错名字等于没申请 |
| openTagList | 可选,需要开放标签时才填 |
注意 appId 这个坑:ticket 与 appId 必须成套使用,混着用一定报错。
四、jsapi_ticket 的获取与缓存
ticket 是签名的基础材料,有效期 7200 秒,但微信对每日获取次数有限制,不能每来一个请求就去拉一次,必须在服务端做集中缓存。获取链路是两步:先用 appid 与 secret 换 access_token,再用它换 ticket。两级缓存都要处理并发——多进程同时发现过期会同时请求,把每日额度快速打空。
GET /cgi-bin/token?grant_type=client_credential&appid=xxx&secret=xxx
GET /cgi-bin/ticket/getticket?access_token=xxx&type=jsapi
几个实战细节:缓存有效期别写 7200,写 7000 甚至更短,留出网络与时钟误差;用「互斥锁加二次检查」,只让一个进程刷新,其他等结果;刷新失败时别立刻删旧 ticket,旧的还能撑一会儿,比全站签名瞬间失效好得多;多机部署把 ticket 放共享缓存,否则额度按机器数量成倍消耗。
五、签名算法与最容易错的三点
把四个值按固定顺序拼成字符串做 SHA1,字段名小写,顺序不能乱。
var raw = 'jsapi_ticket=' + ticket +
'&noncestr=' + nonceStr +
'×tamp=' + timestamp +
'&url=' + url;
var signature = sha1(raw); // 小写十六进制
错误一:url 带了井号后面的部分
微信只认 # 之前的完整地址。页面是 http://m.example.com/list?from=wx#/detail/12 时,参与签名的只有井号前半段。前端取值时要手动截断,不能用整条 location.href。
错误二:url 与当前页面不完全一致
协议、域名、端口、路径、查询参数、参数顺序全都要对得上。常见事故是 http 与 https 混用,或者前端 encodeURIComponent 后传给后端、后端又编码一次,或者中途走了短链跳转。正确姿势:前端从 location.href 取,截掉井号之后的部分,原样传后端,链路上任何一环都不要做 URL 编码。
错误三:noncestr 与 timestamp 不是同一份
服务端算签名用了一组随机数,返回给前端时又生成一组新的。这种低级错误排查极费时间,因为报错和正常签名失败一模一样。返回结果里要原样带上算签名时用的那组值。
六、iOS 与安卓在分享回调上的差异
你在 wx.ready 里注册了分享接口,安卓上能拿到成功回调,iOS 上却从来不触发。原因是两平台机制不同:iOS 侧回调依赖客户端在分享完成后回到页面执行一段逻辑,一旦页面上下文被销毁(比如用户分享后停留在聊天窗口很久),回调就丢了。
- 别把分享成功回调当作发奖励的唯一依据,它只是「可能到达的信号」,不是可靠的业务事实。要发券、发积分,走服务端核验。
- 标题、描述、图片要在
wx.ready里设置,内容变化时要重新设一遍。 - 聊天窗口与朋友圈是两个接口,参数结构不同,朋友圈那条没有描述字段。
- 缩略图尺寸格式不合规会被忽略并回退默认图,看起来就像「没生效」。
七、SPA 路由切换后必须重新 config
用 history 模式做路由时,用户从首页点进详情页,地址变了但页面没刷新,签名里的 url 对应的是旧地址,新地址上的接口就会失败。微信不会自己感知你的路由变化。做法是路由变化后重新请求签名并调 wx.config,用「去井号后的完整 url」做前端缓存 key,同一个 url 只请求一次。别忘了重新注册接口,旧页面注册的回调在新页面上不生效。
八、常见报错与原因对照
| 报错信息 | 真实原因 |
|---|---|
| invalid signature | 签名串拼错、url 不一致、ticket 过期或与 appid 不配套 |
| invalid url domain | 当前域名不在安全域名列表里,或用了未备案域名 |
| permission denied | 公众号未认证,或接口没写进 jsApiList |
| config:fail | 通常伴随上面某条,先开 debug 看卡在哪一步 |
| 签名正确但分享无变化 | 设置写在 wx.ready 之外,或标题过长、缩略图不合规 |
| 接口未定义 | jsApiList 里接口名写错,或脚本版本不含该接口 |
最后一条排查建议:打开 debug,把弹窗里的 url 与签名串完整复制出来,在服务端用同样四个值手工算一遍 SHA1。对不上就是拼接问题;如果手工结果与报错里的一致,问题就只在 url 本身。这套流程能解决九成「明明没错就是不行」。






