抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >
L
O
A
D
I
N
G

本文记录为博客增加安全跳转页面

一、前言

今天在逛博客的时候,偶然发现一篇关于"为博客增加安全跳转中台页面"的文章,先上原文链接给大伙瞅一瞅。

看起来还不错,而且按照文章中的思路实现也不难,唯一比较麻烦的应该是博客适配的问题。

OK ,这么简单好玩的当然得上手试一试咯。

成果展示:

二、实现分析

1. 基本思路

在开始动手之前,我们首先来理一下思路,原文中一共有两个文件,分别是 go.jsgo.html

go.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 定义安全跳转对象
const safeGoFun = {
// TODO: a链接安全跳转(只对文章页,关于页评论 -- 评论要单独丢到waline回调中)
NzcheckLink: async (domName) => {
// 获取文章页非社会分享的a标签
const links = document.querySelectorAll(domName);
if (links) {
// 锚点正则
let reg = new RegExp(/^[#\/].*/);
let relative = new RegExp(/^(?!www\.|http[s]?:\/\/|[\/\\])(.*)$/);
for (let i = 0; i < links.length; i++) {
const ele = links[i];
let eleHref = ele.getAttribute("href"),
eleIsDownLoad = ele.getAttribute("data-download"),
eleRel = ele.getAttribute("rel");
// 如果你的博客添加了Gitter聊天窗,请去掉下方注释 /*|| link[i].className==="gitter-open-chat-button"*/
// 排除:锚点、上下翻页、按钮类、分类、标签
if (
!reg.test(eleHref) && !relative.test(eleHref) &&
eleRel !== "prev" && eleRel !== "next" &&
eleRel !== "category" && eleRel !== "tag" &&
eleHref !== "javascript:void(0);" ) {
// 判断是否下载地址和白名单,是下载拼接 &type=goDown
if (!(await safeGoFun.NzcheckLocalSite(eleHref)) && !eleIsDownLoad) {
// encodeURIComponent() URI编码
ele.setAttribute(
"href",
"/go.html?goUrl=" + encodeURIComponent(eleHref)
);
} else if (
!(await safeGoFun.NzcheckLocalSite(eleHref)) &&
eleIsDownLoad === "goDown"
) {
ele.setAttribute(
"href",
"/go.html?goUrl=" + encodeURIComponent(eleHref) + "&type=goDown"
);
}
}
}
}
},
// 校验白名单,自己博客,local测试
NzcheckLocalSite: async (url) => {
try {
// 白名单地址则不修改href
const safeUrls = ["localhost:4000", "yywen.top","gov.cn"];
let isOthers = false;
for (let i = 0; i < safeUrls.length; i++) {
const ele = safeUrls[i];
if (url.includes(ele)) {
isOthers = true;
break;
}
}
return isOthers;
} catch (err) {
return true;
}
},
};

定义一个对象safeGoFun ,它里面又定义了两个函数NzcheckLinkNzcheckLocalSite

  • NzcheckLink:获取所有 a标签 ,除开不需要处理再全部替换href的值
  • NzcheckLocalSite:校验白名单
1
2
3
Object.keys(safeGoFun).forEach((key) => {
window[key] = safeGoFun[key];
});

将 safeGoFun 对象的属性复制到全局的 window 对象中

1
2
3
4
5
document.addEventListener("DOMContentLoaded", function () {
window.NzcheckLink(
".post-content a:not(.social-share-icon):not(.fancybox):not(.not-check-link)"
);
});

监听页面加载完成事件,完成时调用 NzcheckLink 函数

总的来说,就是将页面中的 a标签 的 href 属性值替换成 /go.html?goUrl=原URL ,然后我们在点击链接的时候就会先跳转到 go.html ,并将原URL当作参数传递过去。

go.html
1
<!-- 原文内容过多不便展示 -->

这里没什么好说的,就是跳转显示页面,将前面传过来的 url 参数和白名单进行校对,符合就进行跳转,不符合就等用户选择

2. 差异性分析

基本情况如下:

博客框架 博客主题 评论区
博主 Hexo Volantis Waline
原创 Hexo Fluid Waline

可以看出就主题有所不同而已,所以实现起来应该大差不差。
不过还是需要考虑到主题所带来的问题,比如本站是启用了 PJAX ,就会产生与原代码不适配的问题。

在 PJAX 中,当使用 AJAX 加载新的页面内容时,原始页面的 JavaScript 事件处理程序不会自动绑定到新加载的内容上。因此,如果 safeGoFun 是在原始页面中绑定的事件处理程序,它不会自动应用于新加载的页面内容。

为了解决这个问题,需要在 PJAX 的回调函数中重新绑定事件处理程序,确保 safeGoFun 方法能够在新加载的内容中正确执行。

还有就是我可以通过 Volantis 主题提供的注入点(headBegin.ejs、headEnd.ejs、bodyBegin.ejs等)来编写 go.js 中的内容来达到相同的效果。其实 go.js 无论是创建在哪里,或者叫啥名都不重要,重点是在你页面加载完成之后你的 NzcheckLink 函数能运行就可以了。

三、实现过程

OK ,思路有了,那就上手试一下吧

1. 接入全局链接

经过上面的分析,基本上实现就没啥难度了,就两步搞定的事。

  1. source/_volantis 目录下创建 bodyBegin.ejs 文件,然后将代码 copy 过了,再改一下配置即可。
source/_volantis/bodyBegin.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<script>
// 定义安全跳转对象
const safeGoFun = {
NzcheckLink: async (domName) => {
// 获取文章页非社会分享的a标签
const links = document.querySelectorAll(domName);
if (links) {
// 锚点正则
let reg = new RegExp(/^[#\/].*/);
let relative = new RegExp(/^(?!www\.|http[s]?:\/\/|[\/\\])(.*)$/);
for (let i = 0; i < links.length; i++) {
const ele = links[i];
let eleHref = ele.getAttribute("href"),
eleIsDownLoad = ele.getAttribute("data-download"),
eleRel = ele.getAttribute("rel");
if (
!reg.test(eleHref) && !relative.test(eleHref) &&
eleRel !== "prev" && eleRel !== "next" &&
eleRel !== "category" &&eleRel !== "tag" &&
eleHref !== "javascript:void(0);" ) {
// 判断是否下载地址和白名单
if (!(await safeGoFun.NzcheckLocalSite(eleHref))) {
// encodeURIComponent() URI编码
ele.setAttribute(
"href",
"/go.html?goUrl=" + encodeURIComponent(eleHref)
);
}
}
}
}
},
// 校验白名单,自己博客,local测试
NzcheckLocalSite: async (url) => {
try {
// 白名单地址则不修改href
const safeUrls = ["localhost:4000", "yywen.top","gov.cn"];
let isOthers = false;
for (let i = 0; i < safeUrls.length; i++) {
const ele = safeUrls[i];
if (url.includes(ele)) {
isOthers = true;
break;
}
}
return isOthers;
} catch (err) {
return true;
}
},
};
// 将 safeGoFun 对象的属性复制到全局的 window 对象中
Object.keys(safeGoFun).forEach((key) => {
window[key] = safeGoFun[key];
});
// 页面加载完成调用 NzcheckLink 函数
document.addEventListener("DOMContentLoaded", function () {
window.NzcheckLink("#l_body a:not(.not-check-link):not(.fancybox)");
});
// 添加Pjax回调函数为全局a标签添加链接检测
volantis.pjax.push(()=>{
window.NzcheckLink("#l_body a:not(.not-check-link):not(.fancybox)");
},'NzcheckLink');

</script>
  1. source 目录下创建 go.html 文件,然后将代码 copy 过了,再改一下配置即可。

博主更改后的代码文件如下:

source/go.html
1
2
<!-- 原文内容过多不便展示,文件链接如下: -->
<!-- https://cloud.yywen.top/d/Resources/Hexo/html/go.html -->

2. 接入评论区链接

我们先来看一下原文是怎么实现的,其代码如下:

1
2
3
4
5
6
7
// 监控滚动到留言板执行回调 -- 添加外链跳转页面
Fluid.utils.waitElementVisible('#waline .wl-cards .wl-item', () => {
setTimeout(() => {
window.NzcheckLink("#waline .wl-cards .wl-item a");
}, 1500);
});
// 代码解释:因为 waline 的初始方法异步,所以需要自己监控判断 waline 的评论 dom 显示以后再执行调用

简单明了,但我们可没有 Fluid.utils.waitElementVisible 这玩意,这就需要我们自己写个 function 来处理了,不过问题不大,注意以下几个问题就🆗了:

  1. 文章或页面是否有开启评论区
  2. 评论区是否有用户发表评论

根据以上问题,博主打算采取轮询计数的方式,因为这个可以解决当页面加载完成但评论还在加载中的问题,可以适当增加轮询间隔和次数来解决评论加载时间过长的问题(比如服务部署不在国内),具体实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 添加Pjax回调函数为评论区添加链接检测
volantis.pjax.push(() => {
function checkElementVisibility(attempts = 0, maxAttempts = 5) {
const targetElement = document.querySelector('#waline .wl-cards .wl-card-item');
const isElementVisible = targetElement && window.getComputedStyle(targetElement).display !== 'none';
if (isElementVisible) {
window.NzcheckLink("#waline .wl-cards a");
} else {
if (attempts < maxAttempts) {
setTimeout(() => {
checkElementVisibility(attempts + 1, maxAttempts);
}, 2000); // 设置适当的轮询间隔
}
}
}
checkElementVisibility();
}, "WacheckLink");

在前面bodyBegin.ejs文件中的 volantis.pjax.push 后面追加以上内容即可(64行)

最后再修改配置文件 _config.yml 跳过指定文件 go.html 的渲染

1
2
skip_render: 
- 'go.html'

使用hexo一键三连(hexo clean && hexo g && hexo d)就可以看到效果啦!

后续更新

Stellar主题适配

在 source 目录下创建 go.html文件

source/go.html
1
2
<!-- 原文内容过多不便展示,文件链接如下,其内容可自行修改 -->
<!-- 文件链接:https://cloud.yywen.top/d/Resources/Hexo/html/go.html -->

修改配置文件_config.yml跳过指定文件的渲染

blog/_config.yml
1
2
skip_render: 
- 'go.html'

使用 Stellar 主题提供的外部文件注入功能,将修改完成的 go.js 上传至 OSS/CDN 以链接注入到网站中

blog/_config.stellar.yml
1
2
3
inject:
script:
- <script async src="https://dogecloud.res.yywen.top/js/stellar-safego.js"></script>
stellar-safego.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
console.log("安全跳转中台页面");
// 定义安全跳转对象
const safeGoFun = {
NzcheckLink: async (domName) => {
// 获取文章页非社会分享的a标签
const links = document.querySelectorAll(domName);
if (links) {
// 锚点正则
let reg = new RegExp(/^[#\/].*/);
let relative = new RegExp(/^(?!www\.|http[s]?:\/\/|[\/\\])(.*)$/);
for (let i = 0; i < links.length; i++) {
const ele = links[i];
let eleHref = ele.getAttribute("href"),
eleIsDownLoad = ele.getAttribute("data-download"),
eleRel = ele.getAttribute("rel");
if (
!reg.test(eleHref) && !relative.test(eleHref) &&
eleRel !== "prev" && eleRel !== "next" &&
eleRel !== "category" && eleRel !== "tag" &&
eleHref !== "javascript:void(0);") {
// 判断是否下载地址和白名单
if (!(await safeGoFun.NzcheckLocalSite(eleHref))) {
// encodeURIComponent() URI编码
ele.setAttribute(
"href",
"/go.html?goUrl=" + encodeURIComponent(eleHref)
);
}
}
}
}
},
// 校验白名单,自己博客,local测试
NzcheckLocalSite: async (url) => {
try {
// 白名单地址则不修改href
const safeUrls = ["localhost:4000", "yywen.top", "gov.cn"];
let isOthers = false;
for (let i = 0; i < safeUrls.length; i++) {
const ele = safeUrls[i];
if (url.includes(ele)) {
isOthers = true;
break;
}
}
return isOthers;
} catch (err) {
return true;
}
},
};
// 将 safeGoFun 对象的属性复制到全局的 window 对象中
Object.keys(safeGoFun).forEach((key) => {
window[key] = safeGoFun[key];
});
// 页面加载完成调用 NzcheckLink 函数
document.addEventListener("DOMContentLoaded", function () {
window.NzcheckLink("#start a:not(.not-check-link):not(.fancybox)");
});

使用hexo一键三连(hexo clean && hexo g && hexo d)就可以看到效果啦!

评论