最近给博客加上了一个实用的功能:友链实时健康监测

起因是备案审查越来越严格。对于个人博客来说,如果友链中的某个域名过期被抢注,摇身一变成为博彩或色情网站,而我的博客还挂着它的链接,那么我的域名很有可能因为“导流违规内容”而被注销备案,甚至面临关站风险。

为了避免这种“人在家中坐,锅从天上来”的情况,手动每天一个个点开检查显然是不现实的。于是,利用 GitHub Actions 和简单的 Node.js 脚本实现自动化监测,就成了最佳解决方案。

实现思路

整体流程分为三个环节:

  1. 数据源:读取博客现有的友链配置文件(YAML 格式)。
  2. 监测脚本:编写 Node.js 脚本,利用并发和连接复用技术,批量检测 HTTP 状态码。
  3. 自动化与通知:通过 GitHub Actions 定时执行,生成状态报告(JSON),并在发现异常时发送邮件通知。
  4. 前端展示:在页面上以不打扰的 UI 风格(黑白极简风格)展示友链状态。

1. 编写监测脚本

scripts/check_links.js 中,我使用了 axios 发送请求,并配合 https.Agent 实现了连接复用(Keep-Alive),同时通过并发池控制请求速率。

这个脚本的核心亮点是:

  • 并发控制:开启 10 个 Worker 并行检测,大幅缩短耗时。
  • 连接复用:开启 keepAlive: true,减少 TCP/TLS 握手开销。
  • 智能重试:优先使用 HEAD 请求(省流量),失败后自动降级为 GET 请求。
  • 异常汇总:将失效链接写入 failed_links_summary.txt,用于后续邮件通知。
const fs = require('fs');
const yaml = require('js-yaml');
const axios = require('axios');
const path = require('path');
const https = require('https');
const http = require('http');

// ... 路径配置 ...

// 优化 Axios 实例:开启 Keep-Alive 复用连接
const axiosInstance = axios.create({
    timeout: 10000,
    maxRedirects: 5,
    httpAgent: new http.Agent({ keepAlive: true }),
    httpsAgent: new https.Agent({ keepAlive: true }),
    headers: {
        'User-Agent': 'Mozilla/5.0 ...' // 模拟浏览器 UA
    },
    validateStatus: (status) => status >= 200 && status < 400
});

async function checkUrl(url) {
    try {
        await axiosInstance.head(url);
        return true;
    } catch (error) {
        try {
            await axiosInstance.get(url); // HEAD 失败尝试 GET
            return true;
        } catch (err2) {
            return false;
        }
    }
}

// ... 并发 Worker 逻辑 ...

2. 配置 GitHub Actions

为了保证数据的实时性,我在 .github/workflows/upy.yml 中配置了定时任务。

  • 定时触发:每 12 小时自动运行一次 (cron: '0 */12 * * *')。
  • 邮件通知:如果发现失效友链,Workflow 会读取脚本生成的摘要文件,并通过邮件推送到我的邮箱。
on:
  schedule:
    - cron: '0 */12 * * *'   # 每12小时自动检查一次

jobs:
  deploy:
    steps:
    # ... 其他步骤 ...

    - name: Check Link Status
      run: node scripts/check_links.js

    - name: Read Failed Links Summary
      if: always()
      run: |
        if [ -f failed_links_summary.txt ]; then
          echo "LINK_SUMMARY<<EOF" >> $GITHUB_ENV
          cat failed_links_summary.txt >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV
        fi        

    # ... 构建步骤 ...

    - name: Send deployment success notification
      if: success()
      uses: dawidd6/action-send-mail@v3
      with:
        # ... 邮件配置 ...
        body: |
          # ... 其他信息 ...
          
          📊 友链监测报告
          ----------------------------------
          ${{ env.LINK_SUMMARY }}          

3. 前端展示:极简设计

为了不破坏博客原本的黑白极简风格,我并没有使用传统的“红/绿”大色块,而是设计了一套低调的状态指示器。

JS 逻辑 (main.js)

前端脚本会异步加载 link_status.json,并智能匹配 URL(自动处理 https 和尾部 / 的差异)。

window.initLinkStatus = function() {
    fetch('/link_status.json?t=' + new Date().getTime())
        .then(response => response.json())
        .then(data => {
            // ... 遍历 DOM 元素 ...
            // getUrlStatus 逻辑:尝试匹配 url, url/, https/http 变体
            
            if (status === 'alive') {
                $item.append('<span class="link-active-badge" title="可访问"></span>');
            } else if (status === 'dead') {
                $item.append('<span class="link-dead-badge" title="无法访问"></span>');
            }
        });
};

CSS 样式 (main.css)

这里花了一些心思调整配色。为了避免“红绿灯”配色的突兀感:

  • 正常状态:使用实心灰点 (#9ca3af),在暗色模式下为浅灰 (#d1d5db)。
  • 失效状态:使用空心灰圈(白色/深色中心 + 灰色边框)。

这种设计既能传达状态信息,又完美融入了黑白主题。

/* Card View - Active (实心灰点) */
.link-active-badge {
    position: absolute;
    top: 8px; right: 8px;
    width: 8px; height: 8px;
    background-color: #9ca3af;
    border-radius: 50%;
    border: 2px solid #fff;
}

/* Card View - Dead (空心灰圈) */
.link-dead-badge {
    position: absolute;
    top: 8px; right: 8px;
    width: 8px; height: 8px;
    background-color: #fff;
    border-radius: 50%;
    border: 2px solid #9ca3af; /* 灰色边框形成空心效果 */
}

总结

这套方案最大的好处是零成本、自动化且优雅

  1. 零成本:完全利用 GitHub Actions 的计算资源。
  2. 自动化:定时检查 + 邮件报警,无需人工干预。
  3. 优雅:前端 UI 克制且统一,不喧宾夺主。

对于注重 SEO 和网站安全的博主来说,这是一个性价比极高的“基建”工作。既对自己负责,也对访客负责。毕竟,谁也不想点击一个链接后发现是 404,或者跳转到奇怪的页面吧。