安知鱼主题实现友链状态检测的备忘

非常漂亮和使用的友链检测工具,很多安知鱼主题都已经使用了,经过几天的努力,终于实现这个过程,分享给大家我的实现思路。

1.准备工作

1.1.创建 js 文件

首先,在 Hexo 根目录下创建 link.js,写入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
const YML = require('yamljs')
const fs = require('fs')

let ls = [],
data = YML.parse(fs.readFileSync('source/_data/link.yml').toString().replace(/(?<=rss:)\s*\n/g, ' ""\n'));

data.forEach((e, i) => {
let j = 2; //获取友链数组的范围(除了最后,前面的都获取)
if (i < j) ls = ls.concat(e.link_list)
});
fs.writeFileSync('./source/flink_count.json', `{"link_list": ${JSON.stringify(ls)},"length":${ls.length}}`)
console.log('flink_count.json 文件已生成。');

其中的 j 表示获取的友链数组的范围,比如你只想要第一组,那么填写 1 即可。

1.2.创建 json 文件

根目录下执行以下内容:

1
2
npm install yamljs --save
node link.js

你将在 [HexoRoot]/source 文件夹下看到 flink_count.json 文件,文件格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"link_list": [
{
"name": "String",
"link": "String",
"avatar": "String",
"descr": "String",
"siteshot": "String"
},{
"name": "String",
"link": "String",
"avatar": "String",
"descr": "String",
"siteshot": "String"
},
// ... 其他76个博客站点信息
],
"length": 77
}

该文件将在执行 hexo g 命令时进入 [BlogRoot]/public 目录下,并上传到网站空间上。

2.配置后端

参考清羽大佬这篇文章:

3.后端输出

我们最终需要后端,给出一个可以调用的 json 文件,文件格式为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"timestamp": "2024-09-19 09:18:49",
"accessible_count": 64,
"inaccessible_count": 12,
"total_count": 76,
"link_status": [
{
"name": "清羽飞扬",
"link": "https://blog.qyliu.top/",
"latency": -1,
},
{
"name": "ChrisKim",
"link": "https://www.zouht.com/",
"latency": 0.76,
},
{
"name": "Akilar",
"link": "https://akilar.top/",
"latency": 3.31,
},
]
}

同时给出一个链接地址:

1
https://friend.example.com/result.json

方便下面的调用。

4.创建 js 文件

在主题根目录下面创建 source/js/flink.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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
(function() {
const STATUS_CACHE_KEY = "statusTagsData";
const STATUS_JSON_URL = "https://friend.liublog.cn/result.json"; // 这里设为你自己的url

let latestData = [];

// 渲染状态标签
function addStatusTags(data) {
if (!Array.isArray(data)) return;

document.querySelectorAll(".flink-list-item, .site-card, .flink-list-card.cf-friends-link").forEach(item => {
// 防止重复添加
if (item.querySelector(".status-tag")) return;

// 获取显示名字
const nameEl = item.querySelector(".flink-item-name, .title.cf-friends-name, .flink-sitename.cf-friends-name");
if (!nameEl) return;
const nameText = nameEl.textContent.trim();

// 根据名字匹配 JSON 数据
const status = data.find(s => s.name === nameText);
if (!status) return;

// 创建状态标签
const tag = document.createElement("div");
tag.classList.add("status-tag");

let text = "未知";
let colorClass = "status-tag-red";

if (status.latency >= 0) {
text = status.latency.toFixed(2) + " s";
colorClass = status.latency <= 2 ? "status-tag-green"
: status.latency <= 5 ? "status-tag-light-yellow"
: status.latency <= 10 ? "status-tag-dark-yellow"
: "status-tag-red";
}

tag.textContent = text;
tag.classList.add(colorClass);

// 保证父容器相对定位
item.style.position = "relative";
item.appendChild(tag);
});
}

// 获取 JSON 数据(带缓存)
function fetchStatusData() {
const cache = localStorage.getItem(STATUS_CACHE_KEY);
if (cache) {
try {
const parsed = JSON.parse(cache);
const cachedData = Array.isArray(parsed.data) ? parsed.data : (parsed.data?.link_status || []);
if (Date.now() - new Date(parsed.timestamp).getTime() < 18e5) { // 30分钟有效
latestData = cachedData;
addStatusTags(latestData);
}
} catch (e) {
console.warn("❌ 解析缓存失败,忽略缓存", e);
}
}

fetch(`${STATUS_JSON_URL}?t=${Date.now()}`)
.then(r => r.json())
.then(json => {
latestData = Array.isArray(json) ? json : (json.link_status || []);
addStatusTags(latestData);
localStorage.setItem(STATUS_CACHE_KEY, JSON.stringify({ data: latestData, timestamp: new Date().toISOString() }));
})
.catch(err => console.error("❌ 获取 result.json 出错:", err));
}

// 监听 DOM 变化,自动渲染新增卡片
function observeNewItems() {
const observer = new MutationObserver(() => addStatusTags(latestData));
observer.observe(document.body, { childList: true, subtree: true });
}

// 初始化
document.addEventListener("DOMContentLoaded", () => {
fetchStatusData();
observeNewItems();
});

document.addEventListener("pjax:complete", () => {
fetchStatusData();
});
})();

如果没有进行过 安知鱼 主题文件的修改,此代码基本上完美支持安知鱼主题,感谢 清羽大佬 的技术支持。

5.创建CSS 文件

在博客主题目录下的 /themes/anzhiyu/source/css 文件夹下创建 custom.css 文件(或者借助其他同文件夹下 CSS 文件引入),输入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.status-tag {
position: absolute;
bottom: 0;
right: 0;
padding: 0 3px;
border-radius: 6px 0 12px 0;
font-size: 10px;
color: #fff;
font-weight: 700;
transition: font-size .3s ease-out, opacity .3s ease-out;
}

.flink-list-item:hover .status-tag {
font-size: 0;
opacity: 0;
}

.status-tag-green { background-color: #599e0b; }
.status-tag-light-yellow { background-color: #fed101; }
.status-tag-dark-yellow { background-color: #f0b606; }
.status-tag-red { background-color: #b90000; }

6.引入 CSS 和 JS 文件

在安知鱼主题的配置文件中搜索 inject ,就会找到如下代码

1
2
3
4
5
6
7
8
9
10
11
# Inject
# Insert the code to head (before '</head>' tag) and the bottom (before '</body>' tag)
# 插入代码到头部 </head> 之前 和 底部 </body> 之前
inject:
head:
# 自定义css
# - <link rel="stylesheet" href="/css/custom.css" media="defer" onload="this.media='all'">

bottom:
# 自定义js
# - <script src="/js/xxx"></script>

head 处进行自定义 CSS 的引入工作

1
- <link rel="stylesheet" href="/css/custom.css" media="defer" onload="this.media='all'">

bottom 处进行自定义 js 的引入工作:

1
- <script src="/js/flink.js"></script>

修改完之后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
# Inject
# Insert the code to head (before '</head>' tag) and the bottom (before '</body>' tag)
# 插入代码到头部 </head> 之前 和 底部 </body> 之前
inject:
head:
# 自定义css
# - <link rel="stylesheet" href="/css/custom.css" media="defer" onload="this.media='all'">
- <link rel="stylesheet" href="/css/custom.css" media="defer" onload="this.media='all'">

bottom:
# 自定义js
# - <script src="/js/xxx"></script>
- <script src="/js/flink.js"></script>

7.修改跨域设置

我是本地服务器安装的程序,使用的是

所以我这里需要设置网站的跨域连接问题,进入宝塔面板,点击左侧栏的 网站

然后点击对应网站的 设置

server 块内找到或添加 location /(或你的 API 路径,如 /api/),粘贴跨域配置代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
location / {
# 允许单个域名(替换为你的前端地址)
add_header Access-Control-Allow-Origin "https://your-frontend.com" always;
# 允许多个域名(用 | 分隔,支持正则)
# add_header Access-Control-Allow-Origin "~^https://(www|admin)\.yourdomain\.com$" always;
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS always;
add_header Access-Control-Allow-Headers Content-Type,Authorization,X-Requested-With always;
# 允许携带Cookie(需配合前端withCredentials: true)
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Max-Age 1728000 always;

if ($request_method = 'OPTIONS') {
return 204;
}

# 原有配置
}

将里面的

1
2
# 允许单个域名(替换为你的前端地址)
add_header Access-Control-Allow-Origin "https://your-frontend.com" always;

域名替换成你要使用这个 result.json 文件的网站域名,

1
2
# 允许单个域名(替换为你的前端地址)
add_header Access-Control-Allow-Origin "https://www.liublog.cn" always;

然后重启 NGINX 服务器。

8.结束语

最后 Hexo 三板斧,就可以在页面侧边栏查看效果。欢迎大家在评论区给予指正,让我们共同打造快节奏时代,不受算法牵制的属于我们自己的小空间!