让自己的网站在微信里分享时好看一些:记一次败中有成的尝试

让自己的网站在微信里分享时好看一些:记一次败中有成的尝试
丁俊尧缘由
有一天,我看到这篇文章:实践:使个人网站在微信/QQ 中被优雅地访问 - 小站背面。我才发现,个人网站如果没有特别在微信那边设置的话,在微信里面分享出来的效果很差,只有一个链接符号,以及网站的标题。
如果是在浏览器端分享,或许还好一点。比如我用 iPhone 的 Safari
浏览器,分享到微信就可以展示标题、头图和简介。这些应该是网页的一些
<meta>
标签在起作用,具体哪些就不知道了,因为我的网站中一堆这种功能的标签。
前面提到的文章中,对于微信的这种限制这么描述:
微信的链接描述和缩略图其实不是自动获取,而是手动定义的。而为什么我们普通人分享时没有看到相关选项呢?因为这是一个白名单功能,是附属于微信公众号的一项功能。简言之,你要想自定义卡片样式,你就需要申请一个公众号,并将自己的域名接入该公众号后台。
雪上加霜的是,这一步要求域名已备案。
我的域名是备案过的,而且我也有公众号,我倒是想知道怎么搞。
但是,我找了一下微信公众号的后台,没有相关的设置。
我搜了一下,发现,原来这个功能用的是微信公众平台的 JS-SDK。文档 里面写的比较详细,可以看看。
我目前的博客是基于 Hexo 的静态博客,使用 安知鱼主题的魔改版本。
基本步骤
微信公众平台的 JS-SDK 中,调用之前需要生成签名。开发者必须在服务器端实现签名的逻辑。
总的来说,想要为在微信内分享的外链添加图片和简介,分为以下几个步骤:
- 绑定域名:公众号设置 → 功能设置 → JS 接口安全域名
- 设置 IP 白名单:仅支持 IPv4 格式的地址;这里也能看到 AppID 和
AppSecret(如果没设置的话生成一下,记下来)
- 在页面引入 JS
文件:
http://res.wx.qq.com/open/js/jweixin-1.6.0.js
- 在白名单所在网络获取
access_token
:
1 | GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET |
1 | // 正常情况下 |
- 在白名单所在网络获取
jsapi_ticket
:
1 | GET https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi |
1 | // 正常情况下 |
- 通过特定的签名算法生成签名
signature
,详见 文档。除了前面得到的jsapi_ticket
,还要传入秒为单位的时间戳timestamp
、随机字符串noncestr
和调用 JS 接口页面的完整 URLurl
(路径要在绑定域名下)。 - 注入权限验证配置:
1 | wx.config({ |
- 自定义“分享给朋友”及“分享到 QQ”按钮的分享内容,以及“分享到朋友圈”及“分享到 QQ 空间”按钮的分享内容:
1 | wx.ready(function () { //需在用户可能点击分享按钮前就先调用 |
解决签名的问题
开发者必须在服务器端实现签名的逻辑,而且 access_token
和
jsapi_ticket
的获取都要在 IP
白名单内进行,并存储,有效期内不应重新请求。因此,我们同样需要在服务器端获取
access_token
和 jsapi_ticket
。
由于 Hexo 生成的文件是一堆静态文件,如果想这么搞就要另起炉灶,专门写一套服务用来生成它们、处理签名。
我不太想写,就找有没有现成可用的,最后找到了一个使用 Express 框架的 JS 项目:wx_jsapi_sign。可以调用它提供的 API,直接得到签名:
1 | GET /api/getWechatJsapiSign/?noncestr=NONCESTR×tamp=TIMESTAMP&url=URL |
1 | {"signature": "aaaabbbbbbbbbbbbbb"} |
后端 Docker 化
由于我几乎所有服务器上的服务都用 Docker 镜像托管,所以我自然就想将它打包为 Docker 镜像,这样部署的时候方便一些。
由于它需要使用 Redis 存储 access_token
和
jsapi_ticket
的信息,因此我使用 Redis 镜像为基础,安装
Node.js,启动。
原来的项目由 config.js
写入配置项:
1 | exports.weixin = { |
我为了部署安全方便,全部改为读取环境变量:
1 | exports.weixin = { |
1 | ENV APP_ID=wx9999999999 |
这样一来,部署时只需要传入环境变量即可:
1 | docker run \ |
在 Docker 里面启动一个服务容易,但是同时启动两个服务,并且还要输出日志,就麻烦了。使用 dumb-init,便可以通过简单的 Shell 脚本,轻松实现同时开启多个服务:
1 |
|
1 | CMD ["/usr/bin/dumb-init", "--", "bash", "start.sh"] # start.sh 即上面的 Shell 脚本 |
如果镜像在 Debian 的基础上构建,用 APT 即可安装,不需要另外添加软件源:
1 | RUN apt-get update -y && \ |
详细的更改见我 fork 的项目:DingJunyao/wx_jsapi_sign。我同时把镜像发布到了 Docker Hub (dingjunyao/wx_jsapi_sign) 和 GitHub Packages(ghcr.io/dingjunyao/wx_jsapi_sign)。
前端的操作
实际上,上面的操作是我后来发现的。我在 Hexo 主题上折腾了半天,才发现还要搞后端。
我上次大段写前端代码,还是在 2018 年做课设的时候。五年过去了,现在的前端早已变得亲妈都不认识了。比如我目前的博客项目,除了 Hexo 及附带的各种插件负责管理、渲染、部署等操作外,使用的主题也采用 pug 模板引擎,以及 stylus 样式引擎。这带来的后果就是,我这种习惯于 HTML + CSS + JS(还是那种最基本的 JS,现在发展的各项技术我早已跟不上了,哪怕是号称入门 ES6 的文档都看不懂) 的外行人,面对现在的代码,只能摸着石头过河,照着已有的代码,猜测其含义,小修小改。调试则变得异常困难:在浏览器上看到的是渲染后的版本,内容、样式有什么不对的,很大程度上只能靠猜。我一般的做法,就是找到可能是标志性的属性和值,在项目里查找这段文本出现的位置,然后据此修改。
我大致的操作如下:
- 照着主题里面引入配置的方式,照着写微信的配置项和引入它的脚本
- 照着主题里面引用 JS 的方法,引入微信的 JS API
- 复制主题中的一个 JS
文件,找到可能用得上的片段,魔改。简单来说,就是通过
fetch
访问签名 API,然后根据得到的签名进行微信 JS-SDK 的各项操作,包括注入配置、应用 API。
我不想大段放代码,有兴趣的可以看 GitHub 上 DingJunyao/hexo-theme-anzhiyu-ding-mod 在 2023-10-17 的提交记录。
这种方式极易漏写变量等字符,因此我花了两天的时间才改好。
解决跨域问题
本地部署好后,我初次测试,发现报跨域相关的错误。简单搜索后,我在后端添加了
cors
包,并使用它。
实际上,只要在添加路由前执行以下语句就可以解决问题了:
1 | const cors = require('cors'); |
但是我希望能够指定允许的域,于是这么写:
1 | const cors = require('cors') |
而 config.whitelist
同样是从环境变量那里读取:
1 | exports.whitelist = (process.env.WHITELIST || '').split(',') |
但是它的值,我琢磨了半天,才发现,如果不处理的话,除了端口号之外,协议也要写上:
1 | https://4ading.com |
折腾了两天才发现没有权限
在浏览器上很难看出来微信相关的代码是否正确运行,需要在微信开发者工具里面才能看到。
注入权限验证配置的时候,我调试了大半天,总算看到
config ok
的提示了。但是,后续的操作仍然失败。然后我发现,config ok
的时候,返回的 jsApiList
列表为空。
我还挺纳闷,把 config
里面的文本改为方法:
1 | jsApiList: [ |
还是不行。
后来我不知为什么,到自己的接口权限里面一看:
这下终于明白了。通过微信认证的公众号才能调用这些功能,而注册主体为个人是搞不了认证的。
搞了两天,才发现自己一开始就没有权限。
败中有成
本来到了这里,我想把代码弃之不用了,不过还是觉得可惜,可是我没有能用的号拿来测试效果。
所幸,微信的开发者工具中还提供了公众平台测试账号,可以测试需要权限的接口。你需要用自己的微信账号登录、关注测试号,然后用它的 appID 和动态变化的 appSecret 来测试。
我用它测试了一下,可行。
趁着还有效,我测试了添加代码后,在微信里面分享出来的效果,结果如下:
所以,虽然自己用不了,但是还是写出了可用的代码。