起因

最近想给博客的友链添加一个自动监测功能,能够显示每个友链的在线状态。这样访问者就能知道哪些朋友的网站还能正常访问

实现过程

1. 定义类型接口

首先创建类型定义文件 types/linkStatus.ts

export interface FriendLink {
    name: string
    title: string
    avatar: string
    link: string
    status?: LinkStatus
    lastChecked?: Date
}

export interface LinkStatus {
    isOnline: boolean
    statusCode?: number
    responseTime?: number
    error?: string
}

2. 实现链接检查服务

创建 utils/simpleLinkChecker.ts

export class SimpleLinkChecker {
    async checkLinks(links: FriendLink[]): Promise<FriendLink[]> {
        const results = await Promise.allSettled(links.map((link) => this.checkSingleLink(link)))

        return links.map((link, index) => {
            const result = results[index]
            if (result.status === 'fulfilled' && result.value) {
                return {
                    ...link,
                    status: result.value.status,
                    lastChecked: result.value.timestamp
                }
            }
            // If failed, mark as offline
            return {
                ...link,
                status: {
                    isOnline: false,
                    error: result.status === 'rejected' ? result.reason.message : 'Check failed'
                },
                lastChecked: new Date()
            }
        })
    }
}

遇到的坑

CORS 问题

第一个大坑是浏览器的 CORS 政策。最初我尝试直接发送 HEAD 请求:

const response = await fetch(link.link, {
    method: 'HEAD',
    mode: 'cors'
})

但大多数网站都不会返回 CORS 头,导致请求失败。解决方案是:

  1. 先尝试 mode: 'cors'
  2. 如果失败,改用 mode: 'no-cors'
  3. 作为后备,使用 Image 对象加载
// Try with ping approach using Image
const img = new Image()
await new Promise((resolve, reject) => {
    img.onload = resolve
    img.onerror = () => reject(new Error('Image load failed'))
    img.src = link.link + '?t=' + Date.now()
})

Vue 列表渲染问题

在实现排序功能时,遇到了一个奇怪的问题:排序后不同的友链显示了相同的头像。这是因为 Vue 的虚拟 DOM 复用机制

问题代码

<div v-for="(friend, index) in displayLinks" :key="index"></div>

解决方案

<div v-for="(friend, index) in displayLinks" :key="friend.link"></div>

使用 friend.link 作为 key,确保每个元素都有唯一的标识

缓存策略

为了避免每次页面加载都检查所有链接,实现了缓存机制:

// Save to localStorage
localStorage.setItem(
    'friendLinksStatus',
    JSON.stringify({
        links: results,
        timestamp: Date.now()
    })
)

// Check if cache is expired (24 hours)
const hoursSinceLastCheck = (now - lastCheck) / (1000 * 60 * 60)
if (hoursSinceLastCheck >= 24) {
    this.checkLinksStatus()
}

最终效果

  1. 自动排序:在线的友链自动排在前面
  2. 状态显示:右上角显示 “ONLINE” 或 “OFFLINE” 标签
  3. 每日更新:每 24 小时自动检查一次
  4. 本地缓存:使用 localStorage 缓存结果