大家好,我是雷布斯。
我们的公众号上发布过很多面经,而且在手机版微信中访问时,点击面经中的题目链接,能跳到对应的题目直接查看答案,确实很方便。
但很多同学也在反馈,平时在公司摸鱼刷题时,如果通过我们的官网查看这些面经,会提示需要去微信中打开,不太友好。由于我们有 PC 上的刷题网站,能不能在 PC 上直接跳转到官网上对应的题目呢?
上个月,我花了半天的时间解决了这个问题,今天就给大家分享下怎么具体的方案。
同学们可以先在电脑上访问:fe.ecool.fun/articles/te... 查看最终的效果。
方案制定
明确下我们的最终目标:为了实现公众号中的链接,在不同的平台上,跳转到不同的地址。也就是手机微信中访问,就跳转到小程序,PC 上访问,就跳转到官网。
大家首先会想到,可以做个中间页,公众号都加这个中间页的地址,在中间页识别平台后,做不同的跳转。
这个方案其实在几年前就考虑过,但由于我们的文章需要支持在小程序中访问(小程序中有面经、技术文章等栏目),如果用中间页的方案,由于小程序属于个人,不支持使用 webview 访问自己的中间页,这个方案只能无奈放弃。
之前官网上的文章,都是跳转到微信公众号进行查看,如果改为自己渲染文章内容,就可以自定义跳转链接这块的逻辑,实现我们的目标,而且还有利于网站的 SEO。
按这个思路,我们做以下工作:
- 爬取和存储文章内容,也就是将文章内容存到自己的数据库。
- 处理文章中的跨域资源,比如文章中有不少图片资源,微信的图片会因为防盗链策略,限制第三方的网站引用,我们需要解决这个问题。
- 支持 SSR 访问,这是做 SEO 比较重要的一点。
数据爬取
首先是要获取文章内容。
随便打开一篇公众号的文章,以 最近的一篇文章 为例,直接查看网页源代码,我们将文章内容作为关键字进行查找,可以在页面中发现对应的区域。
正常情况下,我们可以用正则等方式,获取到完整的文章内容,但后来发现在上面的文章链接后拼上 f=json
后,就会直接以 json
的形式返回完整的文章内容:
这样就能很简单地获取到文章信息了。
接下来应该就是枯燥的爬取全量数据的环节,但我们换个思路,完全可以先读数据库,如果没有正文内容,就通过上面的接口获取并存储到数据库。
用这个思路,可以节省一些写爬虫脚本的时间,完美~
图片跨域
接下来就是将页面渲染出来。
我们拿到正文内容其实是一段 html 代码,在 React 中展示也很建单,直接放到标签的 dangerouslySetInnerHTML
属性中就行。
css
<div dangerouslySetInnerHTML={{ __html: finalHtml }}></div>
但事情往往不会这么一帆风顺,首先是遇到了页面中的图片因跨域无法加载的问题,页面中的图片都变成了这个:
根据我们之前分享的《防盗链的矛与盾》的介绍,这种在个人网站上使用第三方的图片资源的行为,就属于盗链了。
解决的方法也很多,最简单的使用空白的 referer
访问。
html
<meta name="referrer" content="never">
// or
<img referrer="no-referrer" src="xxxx"/>
我选择的是修改 img
标签的 referrer
属性,直接对 html 片段中的相关属性值进行设置:
js
const parser = new DOMParser();
// 解析 HTML 文本为 DOM 文档对象
const doc = parser.parseFromString(html, 'text/html');
// 找到所有的 img 标签
const imgElements = doc.getElementsByTagName('img');
// 遍历每个 img 标签并修改其 src 属性
for (let i = 0; i < imgElements.length; i++) {
const img = imgElements[i];
// 将 img 的 src 属性设置为您想要的自定义内容
img.setAttribute('src', img.getAttribute('data-src') || '');
img.setAttribute('referrerPolicy', 'no-referrer');
}
const finalHtml = doc.documentElement.outerHTML
改完后就能正常加载大部分的图片了,但页面上的背景图都没法展示。
背景图处理
这就是在《防盗链的矛与盾》 中提到的:
在 Chrome 中,即使页面上设置 ,background-image 中的图片请求还是会带上 referer,但火狐中不会带上。
既然 referrer
属性对 background-image
不生效,那我们就用代理的方法解决。
新增一个图片转发的接口,以 egg.js
中的实现为例:
csharp
async transmit(url) {
const { ctx } = this;
const originRes = await ctx.curl(url, {
responseType: "arraybuffer",
});
ctx.set("content-type", originRes.res.headers["content-type"]);
return originRes.data;
}
同时对 html 片段中的图片地址进行处理:
ini
// 获取所有元素节点,解决微信图片作为 background-image 被拦截的问题
const elements = doc.querySelectorAll('*');
// 循环遍历元素
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
// 检查元素是否设置了 background-image 属性
const backgroundImage = element.style.backgroundImage;
if (backgroundImage?.indexOf('https://mmbiz.qpic.cn/') > -1) {
// 使用正则表达式在原有的属性值前添加前缀
const modifiedValue = backgroundImage.replace(
/url\(['"]?(.*?)['"]?\)/,
'url("' + 'https://xx.xxx.xxx/xxx?url=' + '$1")',
);
// 修改 background-image 属性值为新的值
element.style.backgroundImage = modifiedValue;
}
}
跳转链接修改
页面的渲染问题已经基本解决了,还需要处理面经中的跳转链接。
这块的处理也与上面比较类似,就是先从 a 标签中读取到小程序中的跳转地址,解析出题目 id 等参数,然后拼接成 PC 端的访问地址后进行回填,直接看代码:
js
// 找到所有的 img 标签
const aElements = doc.getElementsByTagName('a');
// 遍历每个 img 标签并修改其 src 属性
for (let i = 0; i < aElements.length; i++) {
const a = aElements[i];
const path = a.getAttribute('data-miniprogram-path');
if (path?.indexOf('pages/exerciseDetail/index') === 0) {
const srcParams = new URLSearchParams(path.split('?')?.[1] || '');
if (!!srcParams.get('exerciseKey')) {
a.setAttribute('href', '/topic/' + srcParams.get('exerciseKey'));
a.setAttribute('target', '_blank');
}
}
}
SSR 踩坑
改到这儿,已经基本达成了我们的目标。
但在线上部署后,却出现了页面无法加载的问题,经过排查,是因为我们使用了 SSR ,而在 Node.js
环境中,没有上面使用的 DOMParser
对象。
确实很遗憾了,改了半天却发现 SSR 不好用。
那就只好使用大招:正则表达式替换。
以图片链接为例:
js
// 使用正则表达式匹配 img 标签的 data-src 属性
const imgRegex = /<img\s+[^>]*?data-src=['"](.*?)['"][^>]*?>/g;
finalHtml = finalHtml.replace(imgRegex, function (match, dataSrcValue) {
// 将 data-src 的值赋给 src
return match.replace(
'data-src="' + dataSrcValue + '"',
'src="' + dataSrcValue + '" referrerPolicy="no-referrer"',
);
});
最后
到这儿,就已经完成了迁移的全部工作。
我们在日常的工作中,解决问题时也可以参考这样的思路,想清楚自己面临的问题,思考各种解决方案的优缺点。在具体的技术问题上,也需要先了解问题产生的原因,做好个人的技术沉淀。
最后,还没有使用过我们刷题网站(fe.ecool.fun/)或者刷题小程序(前端面试题宝典)的同学,如果近期准备或者正在找工作,千万不要错过,题库主打无广告和更新快哦。