
非常漂亮和使用的友链检测工具,很多安知鱼主题都已经使用了,经过几天的努力,终于实现这个过程,分享给大家我的实现思路。
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" }, ], "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";
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();
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); }); }
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) { 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)); }
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-Methods GET,POST,PUT,DELETE,OPTIONS always; add_header Access-Control-Allow-Headers Content-Type,Authorization,X-Requested-With always; 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 三板斧,就可以在页面侧边栏查看效果。欢迎大家在评论区给予指正,让我们共同打造快节奏时代,不受算法牵制的属于我们自己的小空间!