友链升级
关于友链升级
一、没有标题
原来的友链页面就是一排卡片,站点挂了也不知道,想整个实时监测
- 探活检测:定时请求每个友链,记录延时和状态
- 分区展示:健康 / 暂存区(疑似失联)/ 墓碑墙(长期失联)
- 截图预览:hover 卡片时显示站点截图
二、探活系统
架构设计
友链探活跑在 后台程序 上,用 Node 的 fetch 对每个友链发 HTTP GET 请求,记录延时和状态到 SQLite 的 friend_link_status 表:
admin-server 启动 → 45 秒后首跑 → 每 6 小时一轮 ↓ 并发 5 个 worker 请求友链(8 秒超时) ↓ 记录:status / latency_ms / fail_count / last_checked_at / http_status ↓ 前台 GET /api/friends/status 公开接口状态分级
| 状态 | 条件 | 徽标颜色 |
|---|---|---|
online | 延时 < 1s | 🟢 绿色 |
slow | 延时 1~3s | 🟡 黄色 |
warn | 延时 > 3s 但成功 | 🟠 橙色 |
offline | 超时 / 网络错误 / DNS 失败 | 🔴 红色 |
很恶心的点
1:bot UA 被 WAF 拦截
一开始用 QiguaiBlog/1.0 这种自定义 UA 去探测,结果有些博客走 Cloudflare,被 WAF 拦截返回挑战页,变成失联换成真实浏览器 UA(Chrome 138 完整 UA)后正常
2:瞬时抖动导致误判
国内机房到海外站点的网络偶尔会抽风 单次失败就判失联有点武将了 所以加了重试机制:网络错误(非 HTTP 4xx/5xx)时等 2.5 秒重试一次,连续两次失败才算真挂
3:失败写 null 抹掉了真实最后在线时间
upsert 的时候失败分支写 last_online_at = null,把之前探测成功记录的真实时间戳给覆盖了。改用 COALESCE(excluded.last_online_at, friend_link_status.last_online_at) —— 新值有就写新值,没有就保留旧值。
前端分区逻辑
根据 fail_count(连续失败次数)分三个区:
- 健康区:正常在线,或失败 1 次显示橙色「波动」徽标(防误伤,先观察)
- 暂存区:失败 2~6 次,虚线卡片,半透明展示
- 墓碑墙:失败 ≥7 次(约 2 天),灰色头像砖,仅显示站点名
这套分级的好处是:暂存区是观察期不是刑场——站点恢复后探测会自动清零失败计数,卡片自动回主区,完全不需要人工干预。
三、截图预览
截图预览是这个升级里最搞心态的部分
第一轮:thum.io + 空闲预热
参考教程里推荐的 thum.io(免费、免 key),前端 hover 时加载 https://image.thum.io/get/width/640/crop/400/png/{url}。
thum.io 有个机制:首次请求触发后台截图并立即返回「生成中」占位图(HTTP 200,~8KB,不触发 onerror,和真图无法区分)。为此加了轮询逻辑——onload 后判断是否是占位图,最多重试 7 次、每次间隔 3.5 秒。
还加了浏览器空闲预热(requestIdleCallback + 顺序队列),在页面空闲时提前请求所有在线站点的截图,让用户 hover 时缓存已热。
第二轮:hover 判断修复
用户反馈「光标放在卡片不会显示抓拍」。排查发现 hover 判断用了 matchMedia('(pointer: coarse)')——带触摸屏的 Windows 笔记本接鼠标时,主指针仍可能被报告为 coarse,导致误杀真实鼠标的 hover 预览。改为 matchMedia('(hover: none)'),只在设备根本不支持 hover 时才跳过。
第三轮:mShots 403 变 thum.io
实测发现 mShots(s0.wp.com)对国内网络返回 HTTP 403(带浏览器 UA/Referer 也拒绝),microlink 25 秒超时,thum.io 200 可用。于是换 thum.io 并适配其占位图机制。
第四轮:thum.io 浏览器缓存钉死
thum.io 响应带 Cache-Control: max-age=86400,浏览器把「生成中」占位图缓存了一整天,24 小时内 hover 都读缓存不会去问 thum 要新图。加了 ?t=Date.now() 时间戳参数强制绕开浏览器缓存。
同时尝试服务器端落盘:探测时从 thum.io 下载真图存到 public/uploads/shots/{host}.png,nginx 直供。但实测发现服务器(国内机房)→ thum.io 出海不通,所有截图文件全部 404。
第五轮:thum.io 从国内网络彻底不通
用户反馈「图片还是显示不出来」。实测确认:
- thum.io 从非国内 IP 返回真图(20KB+),但从国内服务器和浏览器都访问不通
- weserv.nl、Google s2、microlink 全部失败
- thum.io 从国内网络根本不可达,之前「国内可访问」的判断是误判
最终:WordPress mShots
让trae那个ai系统性测试了 10 个截图服务后发现 WordPress mShots(s.wordpress.com/mshots/v1/) 全可:
| 测试项 | 结果 |
|---|---|
| 7 个友链全部返回真图 | 25KB~83KB JPEG |
| 首次请求即真图 | 不需要轮询 |
| CDN 位置 | 日本东京(NRT),国内线路可达 |
| Cache-Control | 12 小时 |
之前记的 s0.wp.com 返回 403 可能是临时问题或不同条件,s.wordpress.com 同样可用且更稳定。最关键的是:mShots 首次请求就返回真实截图,不需要像 thum.io 那样轮询等生成。
最终架构:
服务器探测时从 mShots 下载截图 → 存到 public/uploads/shots/{host}.jpg ↓前端 hover 优先加载本站图(nginx 秒开) ↓404 时无缝回退 mShots 直连 URL ↓mShots 也失败时回退到友链头像 + 信息卡片三级回退

