站点连通性、延迟与测试列表
站点连通性面板使用浏览器 fetch 探测当前测试列表中的目标,并展示可达状态、请求耗时及多轮测试记录。它同时提供列表切换、单卡重测和目标删除入口,通过领域事件把最新测试快照交给其他功能使用。
目的与范围
本页覆盖 ConnectivityTest 的界面入口、活动列表选择、单次探测、批次汇总、多轮最佳结果语义、通知门控与并发保护。重点回答“页面上的可用、不可用和毫秒数究竟如何产生”。
以下内容不在本页展开:ASN 网络互联、Globalping 全球延迟、MTR、DNS/WebRTC 检测,以及报告页面的最终渲染。它们不能与这里的浏览器直连探测混为一谈。
**证据边界:**本文基于 ConnectivityTest.vue 与其 探测和汇总代码。列表导入工具、偏好存储实现、组件中间的状态转换辅助函数和启动函数末尾未在本次阅读范围内,因此不推断其校验规则、存储介质、轮次间隔或卸载清理行为。
概述
该面板有三个不同的操作层级:
| 层级 | 入口 | 实际作用 |
|---|---|---|
| 测试列表 | 列表菜单调用 switchList(list.id) | 切换本次访问使用的活动列表 |
| 整体测试周期 | 刷新按钮调用 handelCheckStart('manual') | 开始新一代测试,区分通知和重置策略 |
| 单个目标 | 毫秒按钮或失败重试按钮 | 调用 checkConnectivityHandler(test, () => { }, true) |
多轮模式采用 best-of-N(保留最佳成功结果),不是平均延迟,也不是必须每轮都成功。卡片主状态和逐轮记录因此可能不同:卡片仍显示可用,某次轮次记录却是失败。
这里的“延迟”是 performance.now() 测得的 fetch Promise 完成耗时,不是 ICMP RTT,也不是完整页面加载耗时。源码不读取响应正文、不检查 HTTP 状态码;Promise 正常完成即进入可用分支。
架构与数据流
列表配置来自 store.userPreferences;运行结果保存在组件的目标状态中。删除目标通过 store.updatePreference 回写配置,而测试汇总通过 connectivity:finished 事件输出。已读探测路径由浏览器直接请求目标,没有调用项目后端代理端点。
图中仅展示已核实的关系;handelCheckStart 后半段的调度实现不在已读范围内,不能据此补画完整轮次调度链路。
测试列表与卡片交互
活动列表选择
lists 读取 userPreferences.connectivityLists?.lists,配置缺失时使用空数组。初始化选择逻辑是:优先采用 connectivityDefaultListId;只有该 ID 存在于列表集合中才接受,否则回退到 MINE_LIST_ID。
1const initialListId = () => {
2 const preferred = userPreferences.value.connectivityDefaultListId;
3 return lists.value.some((l) => l.id === preferred) ? preferred : MINE_LIST_ID;
4};
5const activeListId = ref(initialListId());
6const activeList = computed(() => lists.value.find((l) => l.id === activeListId.value));Source: ConnectivityTest.vue
switchList(id) 在 ID 未变时直接返回;否则更新本地 activeListId 并记录分析事件,没有在该函数内修改默认列表偏好。源码注释明确区分“本次访问切换”和“首选默认列表”。集合变化时,若活动列表已被删除,监听器再次回退到 MINE_LIST_ID。注意,这个回退逻辑本身不创建缺失的 Mine 列表。
只有 lists.length > 1 时才显示列表选择菜单。菜单的管理入口调用 openAddDialog('lists');添加测试卡片调用 openAddDialog('import')。对话框接收 open、initial-tab、active-list-id,具体导入格式及合并策略尚未核实。
卡片展示与重测
- 卡片标题是否为链接由
connectivityCardTitleOpensSite与siteUrlOf(test)共同决定;无法获得地址或偏好关闭时渲染为普通容器。 - 外链使用
_blank和noopener noreferrer。 - 图标加载失败时设置
faviconFailed,随后显示名称首字母;图标加载成功不参与站点可用性判定。 - 非零
test.time显示为可点击毫秒数;当time === 0且状态为失败时显示重试图标。 - 两种重测入口均传入
isManualRun = true,因此不会追加多轮历史。 - 多轮模式显示
totalRounds个记录位置。可见初始化为maxCounts = 9、totalRounds = 1 + maxCounts,即 10 个位置;实际调度间隔不在已核实范围内。
两步删除与配置回写
删除先进入确认态,第二次点击相同目标才删除;确认态在 3000 ms 后自动解除。Mine 列表仅剩一个目标时,直接提示警告,不允许删空。注释说明其他列表允许清空,但是否能删除列表由其他实现负责。
removeTargetById 调用 removeMember(lists.value, activeListId.value, id);返回结果包含 error 时直接退出,否则通过 store.updatePreference('connectivityLists', ...) 保存替换后的列表集合。这里只能确认偏好更新边界,不能据此断言使用 localStorage、数据库或服务端同步。
来源:删除实现。
探测机制与延迟语义
请求方式与超时
以下为实际探测代码节选:
1const controller = new AbortController();
2const timeoutId = setTimeout(() => controller.abort(), 3 * 1200);
3const beginTime = performance.now();
4try {
5 await fetch(test.url, {
6 mode: 'no-cors',
7 method: 'GET',
8 cache: 'no-store',
9 signal: controller.signal,
10 });Source: ConnectivityTest.vue
源码明确说明选择 no-cors GET 是为了避免 HEAD 或图片探测将可达站点误判为不可达;cache: 'no-store' 用于减少缓存造成的近零耗时。超时阈值为 3600 ms,通过 AbortController.abort() 令请求进入失败处理。
成功和异常路径都会清理超时定时器。由于没有检查 response.ok 或状态码,本模块判定的是该请求是否完成,而不是目标业务是否健康。所有异常统一进入相同的失败分支,界面不区分超时、网络错误或浏览器阻止请求。
最佳结果与逐轮历史
recordRound = multipleTests.value && !isManualRun 决定是否参与多轮聚合。成功时耗时四舍五入到整数毫秒,并同步写入本地化 status 和不依赖语言的 statusCode。
1test.statusCode = CONNECTIVITY_STATUS.OK;
2test.mintime = test.mintime === 0 ? testTime : Math.min(test.mintime, testTime);
3test.time = recordRound ? test.mintime : testTime;
4if (recordRound) test.roundResults.push({ tone: testTime < 200 ? 'ok-fast' : 'ok-slow', time: testTime });
5onTestComplete(true);Source: ConnectivityTest.vue
| 数据 | 含义 |
|---|---|
time | 卡片当前显示值;多轮聚合时取最佳值,否则取本次成功耗时 |
mintime | 成功结果中的最小耗时;代码用 0 表示尚无有效最小值 |
roundResults | 每次完成时追加的 { tone, time } 记录 |
status | 本地化状态文案 |
statusCode | CONNECTIVITY_STATUS.OK 或 UNREACHABLE;尚未判定时可为 undefined |
成功耗时 < 200 ms 记录为 ok-fast,否则为 ok-slow;这是一条显示分类阈值,不是超时阈值。
多轮失败时,若 mintime > 0,卡片继续保持可用及最佳时间,但追加 { tone: 'fail', time: 0 }。否则清零显示时间并标记 UNREACHABLE。因此某一轮的失败不会抹去本周期已经取得的成功结果。
来源:成功与失败分支。
批次执行、事件与通知
批次执行顺序
checkAllConnectivity(isAlertToShow, isRefresh, isManualRun) 返回一个等待批次汇总的 Promise:
- 仅当
isAlertToShow为真时将alertToShow置真,后续轮次传入假不会覆盖已经启用的通知意图。 - 增加
passSeq,将当前批次标识保存为passId。 - 如果
isRefresh为真,清除所有卡片的statusCode并把time置零;此处没有清除mintime。 - 对尚无
statusCode的卡片设置“测试中”文案;已有结论的卡片在后续多轮批次启动时保持原判定。 - 快照当前目标 ID 集合,按
50 * indexms 错峰启动各目标请求。 - 通过
Promise.allSettled等待成功与失败请求全部完成,避免单个失败使整批提前退出。 - 如果当前批次已经过时,仅兑现外层 Promise;否则汇总当前仍存在且属于本次调度集合的已判定卡片。
- 更新通知内容、设置
hasEverSettled = true,并发送整个当前网格的结果快照。
Source: ConnectivityTest.vue
快照协议
事件载荷来自实际代码,可作为报告或其他消费者的集成参考:
1emitAppEvent('connectivity:finished', {
2 targets: connectivityTests.map((test) => ({
3 id: test.id,
4 name: test.name,
5 custom: test.id.startsWith('custom-'),
6 statusCode: test.statusCode,
7 time: test.time,
8 mintime: test.mintime,
9 })),
10});Source: ConnectivityTest.vue
事件不是“本批新增结果”列表,而是完整网格的最新快照。它不包含 url、本地化 status 或 roundResults。custom 通过 ID 的 custom- 前缀判断,并非单独从目标配置读取。
批次通知与快照的范围刻意不同:通知只判断该批调度过、完成时仍在网格中且已有状态码的目标;快照输出全部当前卡片。这样,批次中途添加的卡片不会被错误地算成本批失败,中途删除的卡片也不会拖累剩余卡片的汇总结果。
通知门控与入口差异
sendAlert() 同时检查以下条件:尚未发送过、autoShowAltert 开启、alertToShow 已启用、store.allHasLoaded 为真、allRoundsDone 为真。通过后先设置 alertFired,再调用 store.setAlert。通知内容的计算与实际弹出因此是两个步骤。
finalizeMultiTestAlert() 按 mintime > 0 统计至少成功过一次的目标;当数量等于当前目标总数时选择成功通知,而不是要求所有历史轮次都成功。
handelCheckStart 的 trigger | 是否允许本周期通知 | 是否要求重置卡片 | 语义 |
|---|---|---|---|
boot(默认) | 是 | 否 | 启动自动测试 |
manual | 是 | 是 | 面板整体刷新 |
refresh | 否 | 是 | 全局刷新及列表切换场景 |
这里的 manual 触发整个周期,与单卡调用传入 isManualRun = true 不是同一层概念。源码注释说明多轮偏好适用于全部三种周期入口,不仅是启动测试。已读启动函数还会增加 runSeq,并在多轮模式下清除已有 interval、重置计数及完成标记;后续如何启动和结束全部轮次尚未核实。
配置选项与内部常量
下表区分“组件读取的用户偏好”和“组件内部确定的数值”。偏好的全局默认值不在已读源码中,不能把组件读取行为写成配置默认值。
| 选项 | 使用类型 | 默认值或回退 | 作用 |
|---|---|---|---|
connectivityLists | 包含 lists 的对象 | 读取不到列表时使用 [] | 测试列表集合 |
connectivityDefaultListId | 列表 ID | 无匹配时使用 MINE_LIST_ID | 初始活动列表 |
connectivityMultipleTests | 布尔用途 | 全局默认值未核实 | 挂载时快照到 multipleTests,更改在下次重新加载应用 |
popupConnectivityNotifications | 布尔用途 | 全局默认值未核实 | 初始化 autoShowAltert,控制完成通知 |
connectivityCardTitleOpensSite | 布尔用途 | 全局默认值未核实 | 控制标题是否可打开网站 |
simpleMode | 布尔用途 | 全局默认值未核实 | 简洁模式隐藏说明文字 |
| 请求超时 | 数值常量 | 3600 ms | 中止单次请求 |
| 目标错峰 | 数值表达式 | 50 * index ms | 分散同批目标的启动时间 |
| 快慢分类阈值 | 数值常量 | 200 ms | < 200 为 ok-fast |
maxCounts / totalRounds | 数值状态 / 计算值 | 9 / 10 | 多轮计数及历史显示位置 |
| 删除确认窗口 | 数值常量 | 3000 ms | 自动解除确认态 |
内部接口参考
这些函数位于 Vue <script setup> 中,是组件内部接口,不是后端 HTTP API。源码为 JavaScript,没有声明静态参数类型。
| 实际签名 | 参数与返回行为 |
|---|---|
checkConnectivityHandler(test, onTestComplete = () => { }, isManualRun) | async 函数;test 为可变目标状态,回调接收成功布尔值,isManualRun 决定是否跳过轮次历史;正常完成无业务返回值 |
checkAllConnectivity(isAlertToShow, isRefresh, isManualRun) | 参数依次控制通知启用、卡片刷新和探测模式;返回批次结束时兑现的 Promise,无结果载荷 |
handelCheckStart(trigger = 'boot') | async 周期入口,已核实代次更新与触发策略;末尾等待和完成行为未核实 |
switchList(id) | 更新本地活动列表并记录分析事件;相同 ID 不处理,无显式返回值 |
openAddDialog(tab) | 设置初始 tab 并打开对话框,无显式返回值 |
removeTargetById(id) | 删除成员并回写偏好;工具返回 error 时直接退出 |
handleRemoveClick(id) | 执行 Mine 最后一个成员保护及两次点击确认 |
sendAlert() | 通过所有门控才发送通知;无显式返回值 |
finalizeMultiTestAlert() | 根据当前目标是否都曾成功更新通知内容,不直接弹出 |
探测请求的拒绝和超时在 checkConnectivityHandler 中被捕获,并通过回调转换为失败结果;checkAllConnectivity 使用 allSettled,不会因一个正常的探测失败而走整批快速拒绝。其他依赖(如偏好写入工具)是否抛出异常,未从其实现核实。
并发保护、边界与故障解释
两级代次保护
runSeq保护跨周期结果写入。handelCheckStart增加代次;单次处理器开始时捕获runId。请求结束后若代次不同,不更新卡片,但仍调用完成回调,使旧批次可以结束。passSeq保护批次汇总。 每次checkAllConnectivity增加批次号。过期批次在allSettled后仅兑现 Promise,不更新通知、不发结果事件。- 同一周期内允许轮次重叠。 源码明确允许慢请求在下一轮开始后继续写结果,因此跨轮并不采用逐目标“最新请求覆盖一切”的序列锁。
- 忽略结果不等于取消请求。
runSeq检查发生在请求完成或异常之后;可见代码没有集中保存所有控制器并在换周期时立即取消它们。
单卡重测没有独立的目标版本号。若同一周期内对同一个目标重复点击,已读处理器没有防重入判断,多个请求可同时修改同一个目标。多轮历史通过 push 在完成时追加,没有显式轮次索引,所以不能把记录位置无条件解释为严格的请求启动顺序。
来源:并发设计注释与处理器、批次过期检查。
需要注意的边界
| 场景 | 可见实现行为 | 解读注意 |
|---|---|---|
| 超时或任意请求异常 | 进入同一失败分支 | 不能仅凭卡片定位具体网络故障 |
| 多轮已有成功后再失败 | 保留 OK 和 mintime,记录失败轮次 | 主卡片不是最后一轮状态 |
| 单卡重测失败 | 设置 time = 0、UNREACHABLE,不清除 mintime | 当前结果与历史最佳值可以不同 |
| 耗时四舍五入为 0 | 成功时可写入 OK,但 mintime > 0 不成立 | 0 同时承担哨兵值,极小耗时存在语义边界 |
| 批次汇总对象为空 | finished.every(...) 返回真 | 成功通知不必然代表实际测到了至少一个目标 |
| 当前网格为空 | 多轮统计得到 0 === 0 | 最终聚合也可能选择成功文案 |
| 批次中途增删目标 | 通知基于调度 ID 与当前网格交集;事件基于整个网格 | 事件内容与本批判定集合不完全相同 |
这些边界均可从 结果更新、汇总过滤 和 多轮统计 推导;它们不是新增的产品保证。
性能、运维与扩展建议
请求规模。 每个批次为每个目标建立一次定时任务和一个请求 Promise。50 ms 错峰不是并发上限;目标越多,最后一个目标越晚开始。单卡重测还可额外增加请求。不要仅用 3600 ms 超时推断整批固定在 3.6 秒内结束。
诊断顺序。 排查某个站点时,先确认活动列表和实际 test.url,再看浏览器请求是否完成或被中止;确认是在单卡模式还是多轮最佳值模式;最后检查是否发生换周期、批次过期或通知门控未通过。不要用图标是否加载或主卡片是否保留绿色替代请求结果检查。
扩展位置。 若需要区分超时与其他错误,应从处理器的 catch 分支和状态协议同步扩展;若需要严格按轮次展示历史,应引入明确的轮次标识,而不能只依赖完成时 push。若增加报告字段,应在 connectivity:finished 载荷与消费者之间共同约定,不要依赖本地化 status 反推状态。
这些是依据现有控制流给出的工程建议,不代表仓库已经实现了相关扩展。列表导入、安全校验、偏好持久化和卸载清理需要进一步阅读对应实现。本次未读取相关测试正文,因此不声明上述边界已有自动化测试覆盖。
相关链接
- 连通性面板的交互与偏好入口:定位列表选择、卡片按钮和配置读取。
- 浏览器探测与批次结果事件:定位计时、失败处理和报告数据边界。
- 目标删除与周期入口策略:定位删除保护、通知聚合与刷新策略。
本次运行时没有提供其他目录页的实际路径,因此不构造未经核实的 Wiki 跳转链接。全球延迟、ASN、DNS/WebRTC 和报告渲染应查阅各自主题文档,而不是套用本页的浏览器请求语义。