健康检查思路
***{XXX网站}整体排错思路***
- 常见网站连通信测试
1)仅国内百度可访问通表明代理错误
2)仅{XXX网站}无法访问表明代理DNS服务器没有被收集,或个人代理没有设置
- 基础网络信息
1)获取浏览器网络信息
2)获取系统信息
3)获取代理信息
- 站点连通性检测
1)查看链接CDN地址及IP
2){XXX网站} 80、443 连通信时长
- 资源连通性检测
1)访问指定资源,检查连通信时长,时间过长界面会产生搓板情况
- 网络抖动检测
1)访问10次同一资源,查看平均访问差异计算网络质量
- 安全与证书检测
1)手动查看证书,如果非*. {XXX网站}表明代理存在问题
2)严重时需要用户提供路由跟踪信息排查
Windows用户:打开命令提示符,输入 tracert {XXX网站}
Mac用户:打开终端,输入 traceroute {XXX网站}
- 如需{XXX网站}专业人员排查,提供检查日志
代码
文件名 {XXX网站}-check.html
这里已 super.boluobao.ai 为测试,因为他是外网地址
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>super.boluobao.ai 网络连通性专业自检工具</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: #ffffff;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 32px 40px;
}
.header h1 {
font-size: 28px;
font-weight: 600;
margin-bottom: 8px;
}
.header p {
opacity: 0.9;
font-size: 16px;
}
.content {
padding: 32px 40px;
}
.control-panel {
display: flex;
gap: 12px;
margin-bottom: 24px;
flex-wrap: wrap;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
}
.btn-primary:disabled {
background: #a0aec0;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.btn-secondary {
background: #f7fafc;
color: #4a5568;
border: 1px solid #e2e8f0;
}
.btn-secondary:hover {
background: #edf2f7;
}
.progress-bar {
width: 100%;
height: 8px;
background: #e2e8f0;
border-radius: 4px;
overflow: hidden;
margin-bottom: 32px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
width: 0%;
transition: width 0.5s ease;
border-radius: 4px;
}
.section {
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 12px;
margin-bottom: 24px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
.section-header {
background: #f7fafc;
padding: 20px 24px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #e2e8f0;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #2d3748;
}
.section-status {
padding: 6px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
}
.status-success {
background: #c6f6d5;
color: #22543d;
}
.status-failed {
background: #fed7d7;
color: #742a2a;
}
.status-warning {
background: #feebc8;
color: #744210;
}
.status-info {
background: #bee3f8;
color: #2b6cb0;
}
.status-pending {
background: #e2e8f0;
color: #718096;
}
.section-content {
padding: 24px;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
}
.info-card {
background: #f7fafc;
border-radius: 10px;
padding: 20px;
border-left: 4px solid #667eea;
}
.info-label {
font-size: 13px;
color: #718096;
margin-bottom: 6px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.info-value {
font-size: 16px;
font-weight: 500;
color: #2d3748;
word-break: break-all;
}
.test-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid #f0f0f0;
}
.test-item:last-child {
border-bottom: none;
}
.test-name {
font-size: 15px;
color: #2d3748;
font-weight: 500;
}
.test-result {
display: flex;
align-items: center;
gap: 12px;
}
.test-status {
padding: 4px 12px;
border-radius: 16px;
font-size: 13px;
font-weight: 500;
}
.test-details {
font-size: 13px;
color: #718096;
}
.table-container {
overflow-x: auto;
margin-top: 16px;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
th, td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
}
th {
background: #f7fafc;
font-weight: 600;
color: #4a5568;
}
tr:hover {
background: #f7fafc;
}
.chart-container {
height: 300px;
margin-top: 24px;
position: relative;
}
.summary {
background: linear-gradient(135deg, #fff5f5 0%, #fed7d7 100%);
border: 1px solid #fc8181;
border-radius: 12px;
padding: 24px;
margin-top: 24px;
}
.summary h3 {
font-size: 18px;
color: #742a2a;
margin-bottom: 16px;
}
.summary-item {
margin-bottom: 8px;
font-size: 14px;
color: #2d3748;
}
.summary-item strong {
color: #742a2a;
}
.troubleshooting {
background: linear-gradient(135deg, #fffaf0 0%, #feebc8 100%);
border: 1px solid #fbd38d;
border-radius: 12px;
padding: 24px;
margin-top: 16px;
}
.troubleshooting h3 {
font-size: 18px;
color: #744210;
margin-bottom: 16px;
}
.troubleshooting ul {
padding-left: 20px;
font-size: 14px;
color: #2d3748;
}
.troubleshooting li {
margin-bottom: 6px;
}
.log-container {
background: #1a202c;
color: #e2e8f0;
padding: 20px;
border-radius: 12px;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 13px;
height: 250px;
overflow-y: auto;
white-space: pre-wrap;
word-wrap: break-word;
margin-top: 24px;
}
.log-info { color: #90cdf4; }
.log-success { color: #9ae6b4; }
.log-error { color: #fc8181; }
.log-warning { color: #f6e05e; }
/* Chrome DevTools Network Panel Styles */
.network-panel {
background: #f8f9fa;
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
}
.network-timeline {
background: #ffffff;
border-bottom: 1px solid #e0e0e0;
padding: 8px 0;
min-width: 1200px;
}
.timeline-header {
display: flex;
justify-content: space-between;
padding: 0 12px;
font-size: 11px;
color: #5f6368;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.timeline-label {
flex: 1;
text-align: center;
}
.timeline-grid {
height: 8px;
background: repeating-linear-gradient(
90deg,
#e0e0e0,
#e0e0e0 1px,
transparent 1px,
transparent 14.28%
);
margin-top: 4px;
}
.network-timeline-container {
overflow-x: auto;
background: #ffffff;
}
.network-table-container {
overflow-x: auto;
background: #ffffff;
}
#networkTable {
min-width: 1200px;
border-collapse: collapse;
font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
#networkTable th,
#networkTable td {
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid #f0f0f0;
white-space: nowrap;
}
#networkTable th {
background: #f5f5f5;
font-weight: 500;
color: #5f6368;
position: sticky;
top: 0;
z-index: 10;
}
#networkTable tbody tr:hover {
background: #f0f7ff;
}
.col-name { min-width: 200px; }
.col-status { min-width: 80px; }
.col-type { min-width: 100px; }
.col-initiator { min-width: 100px; }
.col-time { min-width: 80px; }
.col-waterfall { min-width: 400px; }
.status-2xx { color: #1a73e8; }
.status-3xx { color: #f9ab00; }
.status-4xx { color: #d93025; }
.status-5xx { color: #d93025; }
.status-ok { color: #1a73e8; }
.status-error { color: #d93025; }
.waterfall-cell {
position: relative;
height: 16px;
}
.waterfall-bar {
position: absolute;
top: 4px;
height: 8px;
border-radius: 2px;
}
.waterfall-bar.html { background: #4285f4; }
.waterfall-bar.css { background: #34a853; }
.waterfall-bar.js { background: #fbbc05; }
.waterfall-bar.image { background: #ea4335; }
.waterfall-bar.font { background: #9c27b0; }
.waterfall-bar.other { background: #607d8b; }
.network-stats {
display: flex;
gap: 24px;
padding: 12px;
background: #f5f5f5;
border-top: 1px solid #e0e0e0;
font-size: 12px;
color: #5f6368;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* Log Controls */
.log-controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 24px;
margin-bottom: 8px;
}
.log-controls h3 {
font-size: 16px;
color: #2d3748;
}
.log-buttons {
display: flex;
gap: 8px;
}
.log-buttons .btn {
padding: 6px 12px;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>super.boluobao.ai 网络连通性专业自检工具</h1>
<p>全面检测与 https://super.boluobao.ai 的网络连接问题,精确定位故障原因</p>
</div>
<div class="content">
<div class="control-panel">
<button id="startTestBtn" class="btn btn-primary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="5 3 19 12 5 21 5 3"></polygon>
</svg>
开始全面检测
</button>
<button id="stopTestBtn" class="btn btn-secondary" disabled>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="6" y="6" width="12" height="12"></rect>
</svg>
停止检测
</button>
<button id="clearLogBtn" class="btn btn-secondary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
清除日志
</button>
</div>
<div class="progress-bar">
<div id="progressFill" class="progress-fill"></div>
</div>
<!-- 1. 常见网站连通信测试 -->
<div class="section">
<div class="section-header">
<h2 class="section-title">1. 常见网站连通信测试</h2>
<span id="commonWebsitesStatus" class="section-status status-pending">待检测</span>
</div>
<div class="section-content">
<div style="margin-bottom: 16px;">
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; margin-bottom: 24px;">
<div id="test-boluobaoAI" class="website-test-item" style="background: #f8f9fa; border: 2px solid #e9ecef; border-radius: 12px; padding: 16px; text-align: center; cursor: pointer; transition: all 0.3s ease;">
<div style="font-size: 32px; margin-bottom: 8px;">🌐</div>
<div style="font-weight: 600; color: #2d3748; margin-bottom: 4px;">boluobaoAI</div>
<div style="font-size: 12px; color: #718096;" id="test-boluobaoAI-status">待检测</div>
</div>
<div id="test-Baidu" class="website-test-item" style="background: #f8f9fa; border: 2px solid #e9ecef; border-radius: 12px; padding: 16px; text-align: center; cursor: pointer; transition: all 0.3s ease;">
<div style="font-size: 32px; margin-bottom: 8px;">🔍</div>
<div style="font-weight: 600; color: #2d3748; margin-bottom: 4px;">百度</div>
<div style="font-size: 12px; color: #718096;" id="test-Baidu-status">待检测</div>
</div>
<div id="test-Google" class="website-test-item" style="background: #f8f9fa; border: 2px solid #e9ecef; border-radius: 12px; padding: 16px; text-align: center; cursor: pointer; transition: all 0.3s ease;">
<div style="font-size: 32px; margin-bottom: 8px;">🔍</div>
<div style="font-weight: 600; color: #2d3748; margin-bottom: 4px;">Google</div>
<div style="font-size: 12px; color: #718096;" id="test-Google-status">待检测</div>
</div>
<div id="test-Facebook" class="website-test-item" style="background: #f8f9fa; border: 2px solid #e9ecef; border-radius: 12px; padding: 16px; text-align: center; cursor: pointer; transition: all 0.3s ease;">
<div style="font-size: 32px; margin-bottom: 8px;">📘</div>
<div style="font-weight: 600; color: #2d3748; margin-bottom: 4px;">Facebook</div>
<div style="font-size: 12px; color: #718096;" id="test-Facebook-status">待检测</div>
</div>
<div id="test-X" class="website-test-item" style="background: #f8f9fa; border: 2px solid #e9ecef; border-radius: 12px; padding: 16px; text-align: center; cursor: pointer; transition: all 0.3s ease;">
<div style="font-size: 32px; margin-bottom: 8px;">𝕏</div>
<div style="font-weight: 600; color: #2d3748; margin-bottom: 4px;">X</div>
<div style="font-size: 12px; color: #718096;" id="test-X-status">待检测</div>
</div>
</div>
</div>
</div>
</div>
<!-- 2. 基础网络信息 -->
<div class="section">
<div class="section-header">
<h2 class="section-title">2. 基础网络信息</h2>
<span id="basicStatus" class="section-status status-pending">待检测</span>
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-card">
<div class="info-label">浏览器访问状态</div>
<div class="info-value" id="browserStatus">待检测</div>
</div>
<div class="info-card">
<div class="info-label">出口IP地址</div>
<div class="info-value" id="exitIp">待检测</div>
</div>
<!-- 新增IP完整信息(中文展示) -->
<div class="info-card">
<div class="info-label">出口IP所在城市</div>
<div class="info-value" id="ipCity">待检测</div>
</div>
<div class="info-card">
<div class="info-label">出口IP所在地区</div>
<div class="info-value" id="ipRegion">待检测</div>
</div>
<div class="info-card">
<div class="info-label">出口IP所在国家</div>
<div class="info-value" id="ipCountry">待检测</div>
</div>
<div class="info-card">
<div class="info-label">出口IP经纬度</div>
<div class="info-value" id="ipLoc">待检测</div>
</div>
<div class="info-card">
<div class="info-label">出口IP邮编</div>
<div class="info-value" id="ipPostal">待检测</div>
</div>
<div class="info-card">
<div class="info-label">出口IP时区</div>
<div class="info-value" id="ipTimezone">待检测</div>
</div>
<div class="info-card">
<div class="info-label">系统时区</div>
<div class="info-value" id="systemTimezone">待检测</div>
</div>
<div class="info-card">
<div class="info-label">系统时间</div>
<div class="info-value" id="systemTime">待检测</div>
</div>
<div class="info-card">
<div class="info-label">网络运营商</div>
<div class="info-value" id="isp">待检测</div>
</div>
<div class="info-card">
<div class="info-label">网络类型</div>
<div class="info-value" id="networkType">待检测</div>
</div>
</div>
</div>
</div>
<!-- 3. 站点连通性检测 -->
<div class="section">
<div class="section-header">
<h2 class="section-title">3. 站点连通性检测</h2>
<span id="siteStatus" class="section-status status-pending">待检测</span>
</div>
<div class="section-content">
<div class="test-item">
<div class="test-name">站点IP地址 (super.boluobao.ai)</div>
<div class="test-result">
<span id="siteIpResult" class="test-status status-pending">待检测</span>
<span id="siteIpDetails" class="test-details"></span>
</div>
</div>
<div class="test-item">
<div class="test-name">HTTP 80端口连通性</div>
<div class="test-result">
<span id="http80Result" class="test-status status-pending">待检测</span>
<span id="http80Details" class="test-details"></span>
</div>
</div>
<div class="test-item">
<div class="test-name">HTTPS 443端口连通性</div>
<div class="test-result">
<span id="https443Result" class="test-status status-pending">待检测</span>
<span id="https443Details" class="test-details"></span>
</div>
</div>
</div>
</div>
<!-- 4. 资源连通性检测 -->
<div class="section">
<div class="section-header">
<h2 class="section-title">4. 资源连通性检测</h2>
<span id="resourceStatus" class="section-status status-pending">待检测</span>
</div>
<div class="section-content">
<!-- 资源列表说明 -->
<div style="background: #d4edda; border: 1px solid #c3e6cb; border-radius: 8px; padding: 16px; margin-bottom: 16px;">
<h3 style="color: #155724; margin-bottom: 8px; font-size: 14px;">✅ 指定资源检测</h3>
<p style="color: #155724; font-size: 13px; line-height: 1.6;">
将检测以下 <strong>11个关键资源</strong> 的连通性:<br>
- 首页及中文页面 (3个)<br>
- 图片资源 (1个)<br>
- API接口 (7个)
</p>
</div>
<!-- Chrome DevTools 风格的Network面板 -->
<div class="network-panel">
<!-- 时间轴 -->
<div class="network-timeline-container">
<div class="network-timeline" id="networkTimeline">
<div class="timeline-header">
<div class="timeline-label">0 ms</div>
<div class="timeline-label">5000 ms</div>
<div class="timeline-label">10000 ms</div>
<div class="timeline-label">15000 ms</div>
<div class="timeline-label">20000 ms</div>
<div class="timeline-label">25000 ms</div>
<div class="timeline-label">30000 ms</div>
</div>
<div class="timeline-grid"></div>
</div>
</div>
<!-- 网络请求表格 -->
<div class="network-table-container">
<table id="networkTable">
<thead>
<tr>
<th class="col-name">名称</th>
<th class="col-status">状态</th>
<th class="col-type">类型</th>
<th class="col-initiator">启动器</th>
<th class="col-time">时间</th>
<th class="col-waterfall">瀑布图</th>
</tr>
</thead>
<tbody id="networkTableBody">
<tr>
<td colspan="6" style="text-align: center; color: #718096; padding: 20px;">等待检测...</td>
</tr>
</tbody>
</table>
</div>
<!-- 统计信息 -->
<div class="network-stats" id="networkStats" style="display: none;">
<span id="requestCount">0 个请求</span>
<span id="loadTime">完成时间 0 ms</span>
</div>
</div>
</div>
</div>
<!-- 5. 网络抖动检测 -->
<div class="section">
<div class="section-header">
<h2 class="section-title">5. 网络抖动检测</h2>
<span id="latencyStatus" class="section-status status-pending">待检测</span>
</div>
<div class="section-content">
<div style="margin-bottom: 16px;">
<!-- 检测1:服务器响应延迟 -->
<div class="test-item" style="border-bottom: 1px solid #e2e8f0; padding: 12px 0;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-weight: 600; color: #2d3748; font-size: 14px;">服务器响应延迟</div>
<div style="font-size: 12px; color: #718096; margin-top: 4px;">通过多次请求测量平均响应时间</div>
</div>
<div style="display: flex; align-items: center; gap: 12px;">
<span id="latency1Status" class="test-status status-pending">待检测</span>
<span id="latency1Value" class="test-details">-</span>
</div>
</div>
</div>
<!-- 检测2:延迟变化趋势 -->
<div class="test-item" style="border-bottom: 1px solid #e2e8f0; padding: 12px 0;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-weight: 600; color: #2d3748; font-size: 14px;">延迟变化趋势</div>
<div style="font-size: 12px; color: #718096; margin-top: 4px;">通过多次测试比较延迟波动情况</div>
</div>
<div style="display: flex; align-items: center; gap: 12px;">
<span id="latency2Status" class="test-status status-pending">待检测</span>
<span id="latency2Value" class="test-details">-</span>
</div>
</div>
</div>
<!-- 检测3:相对网络质量 -->
<div class="test-item" style="padding: 12px 0;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-weight: 600; color: #2d3748; font-size: 14px;">相对网络质量</div>
<div style="font-size: 12px; color: #718096; margin-top: 4px;">评估网络连接是稳定还是波动</div>
</div>
<div style="display: flex; align-items: center; gap: 12px;">
<span id="latency3Status" class="test-status status-pending">待检测</span>
<span id="latency3Value" class="test-details">-</span>
</div>
</div>
</div>
<!-- 延迟曲线图 -->
<div style="margin-top: 24px;">
<h3 style="font-size: 14px; color: #2d3748; margin-bottom: 12px;">📈 10次测试延迟曲线图</h3>
<div style="background: #f7fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 8px; height: 300px;">
<canvas id="latencyChart"></canvas>
</div>
</div>
<!-- 路由追踪说明 -->
<div style="margin-top: 20px;">
<h3 style="font-size: 14px; color: #2d3748; margin-bottom: 8px;">🔍 路由追踪说明(需手动执行)</h3>
<div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 12px;">
<p style="color: #856404; font-size: 13px; line-height: 1.6; margin: 0;">
<strong>Windows用户:</strong>打开命令提示符,输入 <code style="background: #f8f9fa; padding: 2px 6px; border-radius: 4px;">tracert super.boluobao.ai</code><br>
<strong>Mac用户:</strong>打开终端,输入 <code style="background: #f8f9fa; padding: 2px 6px; border-radius: 4px;">traceroute super.boluobao.ai</code>
</p>
</div>
</div>
</div>
</div>
</div>
<!-- 6. 安全与证书检测 -->
<div class="section">
<div class="section-header">
<h2 class="section-title">6. 安全与证书检测</h2>
<span id="securityStatus" class="section-status status-pending">待检测</span>
</div>
<div class="section-content">
<div style="margin-bottom: 16px;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; padding: 24px; color: white;">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
<div style="width: 48px; height: 48px; background: rgba(255,255,255,0.2); border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 24px;">
🔒
</div>
<div>
<h3 style="font-size: 18px; font-weight: 600; margin: 0;">浏览器安全限制</h3>
<p style="font-size: 13px; opacity: 0.9; margin: 4px 0 0 0;">前端无法直接读取证书信息</p>
</div>
</div>
<p style="font-size: 14px; line-height: 1.8; margin: 0 0 20px 0; opacity: 0.95;">
由于浏览器安全策略,前端 JavaScript 没有 API 可以直接读取 SSL 证书的详细信息(如证书名称、有效期、指纹等)。
</p>
<div style="background: rgba(255,255,255,0.15); border-radius: 8px; padding: 16px;">
<h4 style="font-size: 15px; font-weight: 600; margin: 0 0 12px 0; display: flex; align-items: center; gap: 8px;">
<span>📋</span> 如何手动查看证书详情?
</h4>
<ol style="font-size: 14px; line-height: 2; margin: 0; padding-left: 20px;">
<li>访问 <code style="background: rgba(0,0,0,0.2); padding: 2px 8px; border-radius: 4px; font-size: 13px;">https://super.boluobao.ai</code></li>
<li>点击浏览器地址栏左侧的锁图标 🔒</li>
<li>选择"证书"或"连接是安全的"</li>
<li>查看"基本信息"或"详细信息"标签页</li>
<li>确认"公用名 (CN)"是否包含 *.super.boluobao.ai</li>
</ol>
</div>
</div>
</div>
</div>
</div>
<!-- 检测结果汇总 -->
<div id="summarySection" class="summary" style="display: none;">
<h3>检测结果汇总</h3>
<div id="summaryContent"></div>
</div>
<!-- 故障排除建议 -->
<div id="troubleshootingSection" class="troubleshooting" style="display: none;">
<h3>故障排除建议</h3>
<div id="troubleshootingContent"></div>
</div>
<!-- 详细日志 -->
<div class="log-controls">
<h3>详细日志</h3>
<div class="log-buttons">
<button id="copyLogBtn" class="btn btn-secondary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
复制日志
</button>
<button id="exportLogBtn" class="btn btn-secondary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
导出日志
</button>
</div>
</div>
<div class="log-container" id="logContainer"></div>
</div>
</div>
<script>
const TARGET_URL = 'https://super.boluobao.ai';
const TEST_TIMEOUT = 15000;
let isRunning = false;
let abortController = null;
let testResults = {
basic: false,
site: false,
resource: false,
latency: false,
security: false,
commonWebsites: false
};
const logEntries = [];
// DOM元素
const elements = {
startTestBtn: document.getElementById('startTestBtn'),
stopTestBtn: document.getElementById('stopTestBtn'),
clearLogBtn: document.getElementById('clearLogBtn'),
copyLogBtn: document.getElementById('copyLogBtn'),
exportLogBtn: document.getElementById('exportLogBtn'),
progressFill: document.getElementById('progressFill'),
logContainer: document.getElementById('logContainer'),
summarySection: document.getElementById('summarySection'),
summaryContent: document.getElementById('summaryContent'),
troubleshootingSection: document.getElementById('troubleshootingSection'),
troubleshootingContent: document.getElementById('troubleshootingContent'),
latencyChart: null
};
// 日志函数
function log(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString();
logEntries.push({ timestamp, message, type });
const logEntry = document.createElement('div');
logEntry.className = `log-${type}`;
logEntry.textContent = `[${timestamp}] ${message}`;
elements.logContainer.appendChild(logEntry);
elements.logContainer.scrollTop = elements.logContainer.scrollHeight;
}
// 复制日志函数
function copyLog() {
if (logEntries.length === 0) {
alert('暂无日志可复制');
return;
}
const logText = logEntries.map(entry => `[${entry.timestamp}] ${entry.message}`).join('\n');
navigator.clipboard.writeText(logText).then(() => {
log('✓ 日志已复制到剪贴板', 'success');
}).catch(err => {
console.error('复制失败:', err);
alert('复制失败,请手动选择复制');
});
}
// 导出日志函数
function exportLog() {
if (logEntries.length === 0) {
alert('暂无日志可导出');
return;
}
const logText = logEntries.map(entry => `[${entry.timestamp}] ${entry.message}`).join('\n');
const blob = new Blob([logText], { type: 'text/plain;charset=utf-8' });
const now = new Date();
const dateStr = now.getFullYear() +
String(now.getMonth() + 1).padStart(2, '0') +
String(now.getDate()).padStart(2, '0') + '_' +
String(now.getHours()).padStart(2, '0') +
String(now.getMinutes()).padStart(2, '0') +
String(now.getSeconds()).padStart(2, '0');
const filename = `boluobao_network_test_log_${dateStr}.txt`;
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
log(`✓ 日志已导出为 ${filename}`, 'success');
}
// 更新进度条
function updateProgress(percent) {
elements.progressFill.style.width = `${percent}%`;
}
// 带超时的fetch请求
async function fetchWithTimeout(url, options = {}) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), options.timeout || TEST_TIMEOUT);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
throw error;
}
}
// 常见网站数据
const commonWebsites = [
{ name: 'boluobaoAI', url: 'https://super.boluobao.ai' },
{ name: 'Baidu', url: 'https://www.baidu.com' },
{ name: 'Google', url: 'https://www.google.com' },
{ name: 'Facebook', url: 'https://www.facebook.com' },
{ name: 'X', url: 'https://www.x.com' }
];
// 1. 常见网站连通信测试
async function runCommonWebsitesTests() {
log('开始常见网站连通信测试...');
document.getElementById('commonWebsitesStatus').className = 'section-status status-info';
document.getElementById('commonWebsitesStatus').textContent = '检测中';
let successCount = 0;
let failCount = 0;
for (const site of commonWebsites) {
const elementId = `test-${site.name}`;
const statusId = `test-${site.name}-status`;
const element = document.getElementById(elementId);
const statusElement = document.getElementById(statusId);
if (!element || !statusElement) {
log(` ⚠️ 找不到 ${site.name} 的 DOM 元素,跳过`, 'warning');
continue;
}
element.style.borderColor = '#e9ecef';
element.style.background = '#fff3cd';
statusElement.textContent = '检测中...';
statusElement.style.color = '#856404';
log(`测试 ${site.name} (${site.url})...`);
const latency = await testRealSiteConnectivity(site.url);
if (latency < 9999) {
element.style.borderColor = '#48bb78';
element.style.background = '#f0fff4';
statusElement.textContent = `✓ 连通 (${latency}ms)`;
statusElement.style.color = '#276749';
log(` ✓ ${site.name} 连接成功 (${latency}ms)`, 'success');
successCount++;
} else {
element.style.borderColor = '#fc8181';
element.style.background = '#fff5f5';
statusElement.textContent = '✗ 无法连接';
statusElement.style.color = '#c53030';
log(` ✗ ${site.name} 连接失败`, 'error');
failCount++;
}
await new Promise(r => setTimeout(r, 300));
}
document.getElementById('commonWebsitesStatus').className = 'section-status status-success';
document.getElementById('commonWebsitesStatus').textContent = '完成';
testResults.commonWebsites = true;
log(`✓ 常见网站连通信测试完成: ${successCount}/${commonWebsites.length} 个成功`, 'success');
}
// 解析HTML中的资源链接
function parseResourcesFromHTML(html, baseUrl) {
const resources = [];
const base = new URL(baseUrl);
// 匹配所有的资源链接
const resourcePatterns = [
// <link rel="stylesheet" href="...">
{ pattern: /<link[^>]*href=["']([^"']+)["'][^>]*>/gi, type: 'stylesheet', category: 'css' },
// <script src="...">
{ pattern: /<script[^>]*src=["']([^"']+)["'][^>]*>/gi, type: 'script', category: 'js' },
// <img src="...">
{ pattern: /<img[^>]*src=["']([^"']+)["'][^>]*>/gi, type: 'img', category: 'image' },
// <source src="...">
{ pattern: /<source[^>]*src=["']([^"']+)["'][^>]*>/gi, type: 'source', category: 'other' },
];
resourcePatterns.forEach(({ pattern, type, category }) => {
let match;
while ((match = pattern.exec(html)) !== null) {
let src = match[1];
if (src && !src.startsWith('data:') && !src.startsWith('javascript:')) {
try {
const fullUrl = new URL(src, base).href;
const name = fullUrl.split('/').pop() || src;
// 根据扩展名更精确地确定类型
let finalType = type;
let finalCategory = category;
if (name.endsWith('.css')) {
finalType = 'stylesheet';
finalCategory = 'css';
} else if (name.endsWith('.js')) {
finalType = 'script';
finalCategory = 'js';
} else if (/\.(png|jpg|jpeg|gif|svg|webp|ico)$/i.test(name)) {
finalType = name.split('.').pop();
finalCategory = 'image';
} else if (/\.(woff|woff2|ttf|otf|eot)$/i.test(name)) {
finalType = 'font';
finalCategory = 'font';
}
// 避免重复
if (!resources.some(r => r.url === fullUrl)) {
resources.push({
url: fullUrl,
name: name.length > 40 ? name.substring(0, 37) + '...' : name,
type: finalType,
category: finalCategory
});
}
} catch (e) {
// 忽略无效URL
}
}
}
});
return resources;
}
// 2. 基础网络信息检测(已修正+完整中文IP信息)
async function runBasicNetworkTests() {
log('开始基础网络信息检测...');
document.getElementById('basicStatus').className = 'section-status status-info';
document.getElementById('basicStatus').textContent = '检测中';
let allPassed = true;
// 1.1 浏览器访问状态
try {
const isOnline = navigator.onLine;
if (isOnline) {
document.getElementById('browserStatus').textContent = '已连接';
document.getElementById('browserStatus').style.color = '#22543d';
log('✓ 浏览器网络状态正常', 'success');
} else {
document.getElementById('browserStatus').textContent = '离线';
document.getElementById('browserStatus').style.color = '#742a2a';
log('✗ 浏览器处于离线状态', 'error');
allPassed = false;
}
} catch (error) {
document.getElementById('browserStatus').textContent = '未知';
log(`⚠ 浏览器网络状态检测失败: ${error.message}`, 'warning');
}
updateProgress(10);
// 1.2 系统时区和时间
try {
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
document.getElementById('systemTimezone').textContent = timezone;
log(`✓ 系统时区: ${timezone}`, 'success');
const updateTime = () => {
const now = new Date();
document.getElementById('systemTime').textContent = now.toLocaleString();
};
updateTime();
setInterval(updateTime, 1000);
} catch (error) {
document.getElementById('systemTimezone').textContent = '检测失败';
log(`⚠ 系统时间检测失败: ${error.message}`, 'warning');
}
updateProgress(20);
// 1.3 网络类型
try {
// 尝试多种浏览器兼容性的方式获取网络信息
let networkInfo = '';
let hasInfo = false;
let connection = null;
// 1. 标准方式 (Chrome/Edge)
if (navigator.connection) {
connection = navigator.connection;
}
// 2. Firefox 浏览器
else if (navigator.mozConnection) {
connection = navigator.mozConnection;
}
// 3. Safari/WebKit 浏览器
else if (navigator.webkitConnection) {
connection = navigator.webkitConnection;
}
if (connection) {
let typeInfo = '';
let speedInfo = '';
if (connection.effectiveType) {
typeInfo = connection.effectiveType.toUpperCase();
} else if (connection.type) {
typeInfo = connection.type.toUpperCase();
}
if (connection.downlink) {
speedInfo = `${connection.downlink} Mbps`;
} else if (connection.downlinkMax) {
speedInfo = `≤${connection.downlinkMax} Mbps`;
}
if (typeInfo && speedInfo) {
networkInfo = `${typeInfo} (${speedInfo})`;
hasInfo = true;
} else if (typeInfo) {
networkInfo = typeInfo;
hasInfo = true;
} else if (speedInfo) {
networkInfo = speedInfo;
hasInfo = true;
}
}
// 回退方案:如果没有网络类型API不可用,至少显示在线状态
if (!hasInfo) {
if (navigator.onLine) {
networkInfo = '在线';
hasInfo = true;
} else {
networkInfo = '离线';
hasInfo = true;
}
}
if (hasInfo) {
document.getElementById('networkType').textContent = networkInfo;
log(`✓ 网络类型: ${networkInfo}`, 'success');
} else {
document.getElementById('networkType').textContent = '不支持';
log('⚠ 浏览器不支持网络信息API', 'warning');
}
} catch (error) {
// 最坏情况:至少显示在线状态
if (navigator.onLine) {
document.getElementById('networkType').textContent = '在线';
log('✓ 网络类型: 在线', 'success');
} else {
document.getElementById('networkType').textContent = '离线';
log('✓ 网络类型: 离线', 'success');
}
}
updateProgress(30);
// 1.4 出口IP + 完整地理位置信息(中文展示)
let ipDetected = false;
const ipServices = [
{ url: 'https://freeipapi.com/api/json', timeout: 20000 },
{ url: 'https://ipinfo.io/json', timeout: 20000 },
{ url: 'https://api.my-ip.io/v2/ip.json', timeout: 20000 }
];
for (const service of ipServices) {
if (ipDetected) break;
try {
log(`尝试使用 ${service.url} 查询IP信息...`, 'info');
const response = await fetchWithTimeout(service.url, { timeout: service.timeout });
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
let ip, org, city, region, country, loc, postal, timezone;
if (service.url.includes('ip-api.com')) {
ip = data.query;
org = data.isp;
city = data.city;
region = data.regionName;
country = data.country;
loc = data.lat && data.lon ? `${data.lat},${data.lon}` : null;
postal = data.zip;
timezone = data.timezone;
} else if (service.url.includes('ipapi.co')) {
ip = data.ip;
org = data.org;
city = data.city;
region = data.region;
country = data.country;
loc = data.latitude && data.longitude ? `${data.latitude},${data.longitude}` : null;
postal = data.postal;
timezone = data.timezone;
} else if (service.url.includes('ip.sb')) {
ip = data.ip;
org = data.org || data.isp;
city = data.city;
region = data.region;
country = data.country;
loc = data.latitude && data.longitude ? `${data.latitude},${data.longitude}` : null;
postal = data.postal;
timezone = data.timezone;
} else if (service.url.includes('my-ip.io')) {
ip = data.ip;
org = data.isp;
city = data.city;
region = data.region;
country = data.country?.code || data.country;
loc = data.location?.lat && data.location?.lon ? `${data.location.lat},${data.location.lon}` : null;
postal = data.asn?.number || data.zipCode;
timezone = data.timeZone || data.timezone;
} else if (service.url.includes('freeipapi.com')) {
ip = data.ipAddress;
org = data.isp;
city = data.city;
region = data.region;
country = data.countryName;
loc = data.latitude && data.longitude ? `${data.latitude},${data.longitude}` : null;
postal = data.zipCode;
timezone = data.timeZone;
} else {
ip = data.ip;
org = data.org;
city = data.city;
region = data.region;
country = data.country;
loc = data.loc;
postal = data.postal;
timezone = data.timezone;
}
if (ip) {
// 核心IP信息
document.getElementById('exitIp').textContent = ip || '未知';
document.getElementById('isp').textContent = org || '未知运营商';
// 完整扩展信息
document.getElementById('ipCity').textContent = city || '未知城市';
document.getElementById('ipRegion').textContent = region || '未知地区';
document.getElementById('ipCountry').textContent = country || '未知国家';
document.getElementById('ipLoc').textContent = loc || '未知经纬度';
document.getElementById('ipPostal').textContent = postal || '无邮编';
document.getElementById('ipTimezone').textContent = timezone || '未知时区';
log(`✓ 出口IP: ${ip},运营商: ${org || '未知'}`, 'success');
log(`✓ IP地理位置: ${country}-${region}-${city}`, 'success');
ipDetected = true;
break;
}
} catch (error) {
log(`⚠ ${service.url} 查询失败: ${error.message}`, 'warning');
continue;
}
}
if (!ipDetected) {
// 所有IP信息赋值失败,但不影响其他检测
document.getElementById('exitIp').textContent = '未知';
document.getElementById('isp').textContent = '未知';
document.getElementById('ipCity').textContent = '未知';
document.getElementById('ipRegion').textContent = '未知';
document.getElementById('ipCountry').textContent = '未知';
document.getElementById('ipLoc').textContent = '未知';
document.getElementById('ipPostal').textContent = '未知';
document.getElementById('ipTimezone').textContent = '未知';
log('⚠ 所有IP查询服务暂时不可用,但这不影响其他检测', 'warning');
// 不设置 allPassed = false,让检测继续进行
}
updateProgress(50);
document.getElementById('basicStatus').className = 'section-status status-success';
document.getElementById('basicStatus').textContent = '完成';
log('✓ 基础网络信息检测完成', 'success');
testResults.basic = allPassed;
return allPassed;
}
// 3. 站点连通性检测
async function runSiteConnectivityTests() {
log('开始站点连通性检测...');
document.getElementById('siteStatus').className = 'section-status status-info';
document.getElementById('siteStatus').textContent = '检测中';
let allPassed = true;
// 3.1 站点IP地址 - 使用更兼容的检测方式
try {
let ips = '';
let success = false;
// 方法1: 使用Google DNS
try {
const dnsResponse = await fetchWithTimeout('https://dns.google/resolve?name=super.boluobao.ai&type=A', {
timeout: 8000
});
const dnsData = await dnsResponse.json();
if (dnsData.Answer && dnsData.Answer.length > 0) {
ips = dnsData.Answer.map(a => a.data).join(', ');
success = true;
}
} catch (e) {
// 方法1失败,尝试其他方式
}
// 如果方法1失败,尝试方法2
if (!success) {
try {
const dnsResponse = await fetchWithTimeout('https://1.1.1.1/dns-query?name=super.boluobao.ai&type=A', {
headers: { 'Accept': 'application/dns-json' },
timeout: 8000
});
const dnsData = await dnsResponse.json();
if (dnsData.Answer && dnsData.Answer.length > 0) {
ips = dnsData.Answer.map(a => a.data).join(', ');
success = true;
}
} catch (e2) {
// 方法2也失败
}
}
if (success) {
document.getElementById('siteIpResult').className = 'test-status status-success';
document.getElementById('siteIpResult').textContent = '成功';
document.getElementById('siteIpDetails').textContent = ips;
log(`✓ 站点IP地址: ${ips}`, 'success');
} else {
document.getElementById('siteIpResult').className = 'test-status status-warning';
document.getElementById('siteIpResult').textContent = '检测中';
document.getElementById('siteIpDetails').textContent = '浏览器无法直接获取DNS';
log('⚠ 浏览器无法直接获取DNS记录,请访问站点查看连通性', 'warning');
}
} catch (error) {
document.getElementById('siteIpResult').className = 'test-status status-warning';
document.getElementById('siteIpResult').textContent = '检测中';
document.getElementById('siteIpDetails').textContent = '浏览器无法直接获取DNS';
log('⚠ 浏览器无法直接获取DNS记录', 'warning');
}
updateProgress(60);
// 2.2 HTTP 80端口连通性 - 使用图片检测法避免CORS问题
try {
const startTime = performance.now();
const img = new Image();
const imgPromise = new Promise((resolve, reject) => {
img.onload = () => resolve(true);
img.onerror = () => reject(new Error('Connection failed'));
img.src = 'http://super.boluobao.ai/favicon.ico?' + Date.now();
});
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 10000)
);
await Promise.race([imgPromise, timeoutPromise]);
const endTime = performance.now();
const duration = Math.round(endTime - startTime);
document.getElementById('http80Result').className = 'test-status status-success';
document.getElementById('http80Result').textContent = '成功';
document.getElementById('http80Details').textContent = `${duration}ms`;
log(`✓ HTTP 80端口连通性正常,响应时间: ${duration}ms`, 'success');
} catch (error) {
document.getElementById('http80Result').className = 'test-status status-failed';
document.getElementById('http80Result').textContent = '失败';
document.getElementById('http80Details').textContent = error.message;
log(`✗ HTTP 80端口连接失败: ${error.message}`, 'error');
allPassed = false;
}
updateProgress(70);
// 2.3 HTTPS 443端口连通性 - 使用图片检测法避免CORS问题
try {
const startTime = performance.now();
// 使用图片加载方式检测连通性,避免CORS问题
const img = new Image();
const imgPromise = new Promise((resolve, reject) => {
img.onload = () => resolve(true);
img.onerror = () => reject(new Error('Connection failed'));
img.src = TARGET_URL + ':443' + '/favicon.ico?' + Date.now(); // 添加时间戳避免缓存
});
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 10000)
);
await Promise.race([imgPromise, timeoutPromise]);
const endTime = performance.now();
const duration = Math.round(endTime - startTime);
document.getElementById('https443Result').className = 'test-status status-success';
document.getElementById('https443Result').textContent = '成功';
document.getElementById('https443Details').textContent = `${duration}ms`;
log(`✓ HTTPS 443端口连通性正常,响应时间: ${duration}ms`, 'success');
} catch (error) {
document.getElementById('https443Result').className = 'test-status status-failed';
document.getElementById('https443Result').textContent = '失败';
document.getElementById('https443Details').textContent = error.message;
log(`✗ HTTPS 443端口连接失败: ${error.message}`, 'error');
allPassed = false;
}
updateProgress(80);
document.getElementById('siteStatus').className = 'section-status status-success';
document.getElementById('siteStatus').textContent = '完成';
log('✓ 站点连通性检测完成', 'success');
testResults.site = allPassed;
return allPassed;
}
// 4. 资源连通性检测 - 使用指定链接列表
async function runResourceTests() {
log('开始资源连通性检测,将持续30秒...');
document.getElementById('resourceStatus').className = 'section-status status-info';
document.getElementById('resourceStatus').textContent = '检测中 (0/30s)';
const tableBody = document.getElementById('networkTableBody');
tableBody.innerHTML = '<tr><td colspan="6" style="text-align: center; color: #4299e1; padding: 20px;">正在检测资源...</td></tr>';
document.getElementById('networkStats').style.display = 'none';
// 清理之前的performance entries
if (performance.clearResourceTimings) {
performance.clearResourceTimings();
}
const requests = new Map();
let totalTime = 0;
let detectStartTime = performance.now();
// 使用用户指定的链接列表
const resourcesToTest = [
{ url: 'https://super.boluobao.ai/', name: '首页', type: 'document', category: 'html', useImage: true },
{ url: 'https://super.boluobao.ai/zh', name: '中文首页', type: 'document', category: 'html', useImage: true },
{ url: 'https://super.boluobao.ai/zh/home', name: '中文主页', type: 'document', category: 'html', useImage: true },
{ url: 'https://super.boluobao.ai/assets/logo-s.png', name: 'logo-s.png', type: 'png', category: 'image', useImage: true },
{ url: 'https://super.boluobao.ai/api/canva/agent-cashier/task/query/unlimited', name: 'API: task/query', type: 'api', category: 'other' },
{ url: 'https://super.boluobao.ai/api/www/landing-activities/getById?id=46', name: 'API: getById=46', type: 'api', category: 'other' },
{ url: 'https://super.boluobao.ai/api/www/landing-activities/getById?id=52', name: 'API: getById=52', type: 'api', category: 'other' },
{ url: 'https://super.boluobao.ai/api/canva/agent-cashier/pricing/timeVariantConfig', name: 'API: pricing/config', type: 'api', category: 'other' },
{ url: 'https://super.boluobao.ai/api/www/boluobao/member/free/power', name: 'API: member/power', type: 'api', category: 'other' },
{ url: 'https://super.boluobao.ai/api/www/boluobao/member/account', name: 'API: member/account', type: 'api', category: 'other' },
{ url: 'https://super.boluobao.ai/api/www/boluobao/member/packages?packageType=TRAIN&libId=0&language=zh', name: 'API: member/packages', type: 'api', category: 'other' },
];
return new Promise((resolve) => {
// 更新状态显示
let elapsed = 0;
const statusInterval = setInterval(() => {
elapsed += 1;
document.getElementById('resourceStatus').textContent = `检测中 (${elapsed}/30s)`;
}, 1000);
// 清空之前的 performance 记录
if (performance.clearResourceTimings) {
performance.clearResourceTimings();
}
// 记录我们要请求的 URL
const expectedUrls = new Set(resourcesToTest.map(r => r.url));
// 开始请求所有资源
log(`开始请求 ${resourcesToTest.length} 个资源...`, 'info');
resourcesToTest.forEach((resource, index) => {
log(` [${index + 1}] ${resource.name}: ${resource.url}`, 'info');
});
resourcesToTest.forEach((resource, index) => {
requestResource(resource, index * 300); // 300ms间隔
});
// 完全禁用 performance API 收集,避免缓存干扰
const checkInterval = null;
// 30秒后结束
setTimeout(() => {
finishCollection(checkInterval);
}, 30000);
// 添加手动请求的资源
async function requestResource(resource, delay = 0) {
await new Promise(r => setTimeout(r, delay));
const startTime = performance.now() - detectStartTime;
try {
const requestStartTime = performance.now();
// 使用 Image 方式检测(适用于所有资源,避免 CORS 问题)
await new Promise((resolve, reject) => {
const img = new Image();
const timeout = setTimeout(() => reject(new Error('Timeout')), 10000);
let done = false;
const handleResult = (isSuccess) => {
if (done) return;
done = true;
clearTimeout(timeout);
const latency = performance.now() - requestStartTime;
if (latency < 100 || !navigator.onLine) {
reject(new Error('Cache or offline'));
} else if (isSuccess) {
resolve();
} else {
// 我们自己的网站资源,即使是error只要有延迟也可能成功
if (latency >= 200) {
resolve();
} else {
reject(new Error('Too fast'));
}
}
};
img.onload = () => handleResult(true);
img.onerror = () => handleResult(false);
const random = Math.random().toString(36).substring(2, 15);
img.src = resource.url + '?no_cache=' + Date.now() + '&r=' + random;
});
const endTime = performance.now();
const duration = endTime - requestStartTime;
// 直接添加到列表中(成功)
const url = resource.url;
if (!requests.has(url)) {
let name = url.split('/').pop() || url;
if (name.length > 40) {
name = name.substring(0, 37) + '...';
}
const req = {
name: name,
fullUrl: url,
type: resource.type,
initiator: 'direct',
category: resource.category,
startTime: startTime,
duration: duration,
statusText: '成功',
statusClass: 'status-ok',
fromCache: false
};
requests.set(url, req);
totalTime = Math.max(totalTime, startTime + duration);
updateTable();
}
log(`✓ 成功加载: ${resource.name} (${Math.round(duration)}ms)`, 'success');
} catch (error) {
// 添加失败的资源
const url = resource.url;
if (!requests.has(url)) {
let name = url.split('/').pop() || url;
if (name.length > 40) {
name = name.substring(0, 37) + '...';
}
const req = {
name: name,
fullUrl: url,
type: resource.type,
initiator: 'direct',
category: resource.category,
startTime: startTime,
duration: 10000,
statusText: '失败',
statusClass: 'status-error',
fromCache: false
};
requests.set(url, req);
totalTime = Math.max(totalTime, startTime + 10000);
updateTable();
}
log(`✗ 加载失败: ${resource.name} - ${error.message}`, 'error');
}
}
function addResourceFromEntry(entry) {
const url = entry.name;
if (requests.has(url)) {
return;
}
let name = url.split('/').pop() || url;
if (name.length > 40) {
name = name.substring(0, 37) + '...';
}
if (!name || name === '') {
name = url;
}
// 判断资源类型
let type = 'other';
let category = 'other';
if (entry.initiatorType) {
type = entry.initiatorType;
if (type === 'link') type = 'css';
if (type === 'script') category = 'js';
if (type === 'css' || type === 'link') category = 'css';
if (type === 'img' || type === 'image') category = 'image';
}
if (url.endsWith('.css')) { category = 'css'; type = 'stylesheet'; }
if (url.endsWith('.js')) { category = 'js'; type = 'script'; }
if (/\.(png|jpg|jpeg|gif|svg|webp)$/i.test(url)) {
category = 'image';
type = url.split('.').pop();
}
if (/\.(woff|woff2|ttf|otf|eot)$/i.test(url)) {
category = 'font';
type = 'font';
}
if (url === TARGET_URL || url === TARGET_URL + '/') {
category = 'html';
type = 'document';
}
const duration = Math.round(entry.duration);
const startTimeEntry = Math.round(entry.startTime);
const req = {
name: name,
fullUrl: url,
type: type,
initiator: entry.initiatorType || 'fetch',
category: category,
startTime: startTimeEntry,
duration: duration,
statusText: '200',
statusClass: 'status-ok',
fromCache: entry.transferSize === 0 && (entry.encodedBodySize > 0 || entry.decodedBodySize > 0)
};
requests.set(url, req);
totalTime = Math.max(totalTime, startTimeEntry + duration);
updateTable();
}
// 更新表格显示
function updateTable() {
const tableBody = document.getElementById('networkTableBody');
tableBody.innerHTML = '';
const requestArray = Array.from(requests.values());
for (const req of requestArray) {
// 瀑布图位置和宽度
const waterfallLeft = (req.startTime / 30000) * 100;
const waterfallWidth = Math.max((req.duration / 30000) * 100, 0.5);
// 时间格式化
const timeText = req.duration > 0 ? Math.round(req.duration) + ' ms' : '-';
const row = document.createElement('tr');
row.innerHTML = `
<td class="col-name" title="${req.fullUrl}">${req.name}</td>
<td class="col-status ${req.statusClass}">${req.statusText}</td>
<td class="col-type">${req.type}</td>
<td class="col-initiator">${req.initiator}</td>
<td class="col-time">${timeText}</td>
<td class="col-waterfall">
<div class="waterfall-cell">
<div class="waterfall-bar ${req.category}"
style="left: ${waterfallLeft}%; width: ${waterfallWidth}%;">
</div>
</div>
</td>
`;
tableBody.appendChild(row);
}
// 更新统计
document.getElementById('networkStats').style.display = 'flex';
document.getElementById('requestCount').textContent = requestArray.length + ' 个请求';
document.getElementById('loadTime').textContent = '完成时间 ' + Math.round(totalTime) + ' ms';
}
// 结束收集
function finishCollection(checkInterval) {
clearInterval(statusInterval);
if (checkInterval) {
clearInterval(checkInterval);
}
updateProgress(95);
document.getElementById('resourceStatus').className = 'section-status status-success';
document.getElementById('resourceStatus').textContent = '完成';
if (requests.size > 0) {
log(`✓ 资源连通性检测完成,共收集 ${requests.size} 个资源`, 'success');
// 输出所有资源信息到日志
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'info');
log('资源详情列表:', 'info');
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'info');
const requestArray = Array.from(requests.values());
requestArray.forEach((req, index) => {
const logMsg = `[${index + 1}] 名称: ${req.name} | 类型: ${req.type} | 启动器: ${req.initiator} | 时间: ${req.duration}ms | URL: ${req.fullUrl}`;
log(logMsg, 'info');
});
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'info');
} else {
log('资源连通性检测完成,但未收集到有效资源(可能是跨域限制)', 'warning');
}
testResults.resource = true;
resolve(true);
}
});
}
// 5. 网络抖动检测
async function runLatencyTests() {
log('开始网络性能检测...');
document.getElementById('latencyStatus').className = 'section-status status-info';
document.getElementById('latencyStatus').textContent = '检测中';
// 设置初始状态
document.getElementById('latency1Status').className = 'test-status status-info';
document.getElementById('latency1Status').textContent = '检测中';
document.getElementById('latency2Status').className = 'test-status status-info';
document.getElementById('latency2Status').textContent = '检测中';
document.getElementById('latency3Status').className = 'test-status status-info';
document.getElementById('latency3Status').textContent = '检测中';
const testCount = 10;
const latencyResults = [];
// 使用Image对象测试延迟(避免CORS问题)
for (let i = 0; i < testCount; i++) {
const testUrl = TARGET_URL + '/favicon.ico?t=' + Date.now() + '&i=' + i;
const latency = await testSingleLatency(testUrl);
latencyResults.push(latency);
if (latency < 9999) {
log(` 第${i + 1}次测试: ${latency}ms`, 'info');
} else {
log(` 第${i + 1}次测试: 失败`, 'warning');
}
await new Promise(r => setTimeout(r, 300));
}
// 计算统计数据
const validLatencies = latencyResults.filter(l => l < 9999);
if (validLatencies.length > 0) {
const avgLatency = Math.round(validLatencies.reduce((a, b) => a + b, 0) / validLatencies.length);
const minLatency = Math.min(...validLatencies);
const maxLatency = Math.max(...validLatencies);
// 计算延迟变化
let jitterSum = 0;
for (let i = 1; i < validLatencies.length; i++) {
jitterSum += Math.abs(validLatencies[i] - validLatencies[i - 1]);
}
const avgJitter = validLatencies.length > 1 ? Math.round(jitterSum / (validLatencies.length - 1)) : 0;
// 检测1:服务器响应延迟
document.getElementById('latency1Status').className = 'test-status status-success';
document.getElementById('latency1Status').textContent = '正常';
document.getElementById('latency1Value').textContent = `平均${avgLatency}ms (${minLatency}ms - ${maxLatency}ms)`;
// 检测2:延迟变化趋势
let trendText = '';
let trendStatus = '';
if (avgJitter < 50) {
trendText = '稳定';
trendStatus = 'status-success';
} else if (avgJitter < 100) {
trendText = '轻微波动';
trendStatus = 'status-warning';
} else {
trendText = '波动较大';
trendStatus = 'status-error';
}
document.getElementById('latency2Status').className = 'test-status ' + trendStatus;
document.getElementById('latency2Status').textContent = trendText;
document.getElementById('latency2Value').textContent = `平均变化${avgJitter}ms`;
// 检测3:相对网络质量
let qualityText = '';
let qualityStatus = '';
if (avgLatency < 100 && avgJitter < 50) {
qualityText = '优秀';
qualityStatus = 'status-success';
} else if (avgLatency < 300 && avgJitter < 100) {
qualityText = '良好';
qualityStatus = 'status-success';
} else if (avgLatency < 500 && avgJitter < 200) {
qualityText = '一般';
qualityStatus = 'status-warning';
} else {
qualityText = '较差';
qualityStatus = 'status-error';
}
document.getElementById('latency3Status').className = 'test-status ' + qualityStatus;
document.getElementById('latency3Status').textContent = qualityText;
document.getElementById('latency3Value').textContent = `延迟${avgLatency}ms / 波动${avgJitter}ms`;
// 绘制曲线图
drawLatencyChart(latencyResults);
log(`✓ 网络性能检测完成: 平均${avgLatency}ms, 变化${avgJitter}ms, 质量${qualityText}`, 'success');
} else {
document.getElementById('latency1Status').className = 'test-status status-error';
document.getElementById('latency1Status').textContent = '失败';
document.getElementById('latency1Value').textContent = '测试失败';
document.getElementById('latency2Status').className = 'test-status status-error';
document.getElementById('latency2Status').textContent = '失败';
document.getElementById('latency2Value').textContent = '测试失败';
document.getElementById('latency3Status').className = 'test-status status-error';
document.getElementById('latency3Status').textContent = '失败';
document.getElementById('latency3Value').textContent = '测试失败';
log('网络性能检测失败', 'error');
}
document.getElementById('latencyStatus').className = 'section-status status-success';
document.getElementById('latencyStatus').textContent = '完成';
testResults.latency = true;
}
// 单个延迟测试(用于网络检测)
function testSingleLatency(url) {
return new Promise((resolve) => {
if (!navigator.onLine) {
resolve(9999);
return;
}
const img = new Image();
const startTime = performance.now();
let done = false;
const timeoutId = setTimeout(() => {
if (!done) resolve(9999);
}, 8000);
const handleResult = (isSuccess) => {
if (done) return;
done = true;
clearTimeout(timeoutId);
const latency = Math.round(performance.now() - startTime);
// 只有真正成功加载才算连通
if (isSuccess) {
resolve(latency);
} else {
resolve(9999);
}
};
img.onload = () => handleResult(true);
img.onerror = () => handleResult(false);
const random = Math.random().toString(36).substring(2, 15);
const cacheBuster = '?no_cache=' + Date.now() + '&r=' + random;
img.src = url.includes('?') ? url + '&no_cache=' + Date.now() + '&r=' + random : url + cacheBuster;
});
}
// 网站连通性测试 - 平衡版
function testRealSiteConnectivity(url) {
return new Promise((resolve) => {
if (!navigator.onLine) {
resolve(9999);
return;
}
const img = new Image();
const startTime = performance.now();
let done = false;
const timeoutId = setTimeout(() => {
if (!done) resolve(9999);
}, 10000);
const handleResult = (isSuccess) => {
if (done) return;
done = true;
clearTimeout(timeoutId);
const latency = Math.round(performance.now() - startTime);
// 只有真正成功加载才算连通
if (isSuccess) {
resolve(latency);
} else {
resolve(9999);
}
};
img.onload = () => handleResult(true);
img.onerror = () => handleResult(false);
const random = Math.random().toString(36).substring(2, 12);
let finalUrl;
finalUrl = url.replace(/\/$/, '') + '/favicon.ico?no_cache=' + Date.now() + '&r=' + random;
img.src = finalUrl;
});
}
// 绘制延迟曲线图
function drawLatencyChart(latencyData) {
const canvas = document.getElementById('latencyChart');
const ctx = canvas.getContext('2d');
// 设置画布尺寸
const container = canvas.parentElement;
canvas.width = container.clientWidth - 16;
canvas.height = container.clientHeight - 16;
const padding = 30;
const chartWidth = canvas.width - padding * 2;
const chartHeight = canvas.height - padding * 2;
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制背景网格
ctx.strokeStyle = '#e2e8f0';
ctx.lineWidth = 1;
// 横线
for (let i = 0; i <= 4; i++) {
const y = padding + (chartHeight / 4) * i;
ctx.beginPath();
ctx.moveTo(padding, y);
ctx.lineTo(canvas.width - padding, y);
ctx.stroke();
}
// 找到最大值,处理失败值
const validData = latencyData.filter(l => l < 9999);
const maxLatency = validData.length > 0 ? Math.max(...validData) * 1.2 : 500;
// 绘制曲线
ctx.strokeStyle = '#667eea';
ctx.lineWidth = 3;
ctx.beginPath();
const stepX = chartWidth / (latencyData.length - 1);
for (let i = 0; i < latencyData.length; i++) {
const x = padding + i * stepX;
const y = padding + chartHeight - (latencyData[i] / maxLatency) * chartHeight;
if (latencyData[i] >= 9999) {
// 失败值画红色点
ctx.fillStyle = '#e53e3e';
ctx.beginPath();
ctx.arc(x, padding + chartHeight - 15, 6, 0, Math.PI * 2);
ctx.fill();
continue;
}
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
// 绘制数据点
for (let i = 0; i < latencyData.length; i++) {
if (latencyData[i] >= 9999) continue;
const x = padding + i * stepX;
const y = padding + chartHeight - (latencyData[i] / maxLatency) * chartHeight;
ctx.fillStyle = '#667eea';
ctx.beginPath();
ctx.arc(x, y, 6, 0, Math.PI * 2);
ctx.fill();
// 绘制数值
ctx.fillStyle = '#4a5568';
ctx.font = '14px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(`${latencyData[i]}ms`, x, y - 12);
}
// 绘制坐标轴
ctx.strokeStyle = '#718096';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(padding, padding);
ctx.lineTo(padding, canvas.height - padding);
ctx.lineTo(canvas.width - padding, canvas.height - padding);
ctx.stroke();
// 绘制标签
ctx.fillStyle = '#718096';
ctx.font = '14px sans-serif';
ctx.textAlign = 'center';
for (let i = 0; i < latencyData.length; i++) {
const x = padding + i * stepX;
ctx.fillText(`#${i + 1}`, x, canvas.height - padding + 22);
}
ctx.textAlign = 'right';
ctx.fillText(`0ms`, padding - 5, canvas.height - padding + 5);
ctx.fillText(`${Math.round(maxLatency)}ms`, padding - 5, padding + 5);
}
// 6. 安全与证书检测
async function runSecurityTests() {
log('开始安全与证书检测...');
document.getElementById('securityStatus').className = 'section-status status-success';
document.getElementById('securityStatus').textContent = '完成';
testResults.security = true;
log('✓ 安全与证书检测完成,请查看说明手动验证证书', 'success');
}
// 生成检测总结
function generateSummary() {
elements.summarySection.style.display = 'block';
elements.troubleshootingSection.style.display = 'block';
let summaryHtml = '';
let troubleshootingHtml = '';
summaryHtml += `<div class="summary-item"><strong>常见网站测试:</strong> ${testResults.commonWebsites ? '完成' : '未完成'}</div>`;
summaryHtml += `<div class="summary-item"><strong>基础网络检测:</strong> ${testResults.basic ? '正常' : '存在异常'}</div>`;
summaryHtml += `<div class="summary-item"><strong>站点连通性:</strong> ${testResults.site ? '正常' : '连接失败'}</div>`;
summaryHtml += `<div class="summary-item"><strong>资源连通性:</strong> ${testResults.resource ? '全部成功' : '部分/全部失败'}</div>`;
summaryHtml += `<div class="summary-item"><strong>网络抖动检测:</strong> ${testResults.latency ? '完成' : '未完成'}</div>`;
summaryHtml += `<div class="summary-item"><strong>安全与证书检测:</strong> 请按照说明手动查看证书</div>`;
summaryHtml += `<div class="summary-item"><strong>总体状态:</strong> ${testResults.commonWebsites && testResults.basic && testResults.site && testResults.resource && testResults.latency ? '正常' : '异常'}</div>`;
troubleshootingHtml += `<ul>`;
troubleshootingHtml += `<li>检查网络连接是否正常,尝试切换Wi-Fi/有线网络</li>`;
troubleshootingHtml += `<li>清除浏览器缓存和Cookie后重试</li>`;
troubleshootingHtml += `<li>检查防火墙/杀毒软件是否拦截了对super.boluobao.ai的访问</li>`;
troubleshootingHtml += `<li>尝试更换DNS服务器(如8.8.8.8或223.5.5.5)</li>`;
troubleshootingHtml += `<li>如果正在使用VPN,请尝试连接其他区域节点</li>`;
troubleshootingHtml += `<li>尝试更换VPS节点,选择延迟更低的服务器</li>`;
troubleshootingHtml += `</ul>`;
elements.summaryContent.innerHTML = summaryHtml;
elements.troubleshootingContent.innerHTML = troubleshootingHtml;
log('检测总结已生成', 'info');
}
// 清理浏览器缓存的辅助函数
async function clearBrowserCache() {
log('🧹 正在清理缓存...', 'info');
try {
// 1. 清理 performance 记录
if (performance.clearResourceTimings) {
performance.clearResourceTimings();
}
// 2. 尝试强制请求随机资源来破坏缓存
for (let i = 0; i < 3; i++) {
try {
const randomUrl = TARGET_URL + '/?clearCache=' + Math.random() + '&t=' + Date.now();
const img = new Image();
await new Promise((resolve) => {
img.onload = resolve;
img.onerror = resolve;
img.src = randomUrl;
setTimeout(resolve, 500);
});
} catch (e) {
// 忽略错误
}
await new Promise(r => setTimeout(r, 100));
}
log('✅ 缓存清理完成', 'success');
} catch (e) {
log('⚠️ 缓存清理跳过', 'warning');
}
}
// 主检测流程
async function runAllTests() {
if (isRunning) return;
isRunning = true;
elements.startTestBtn.disabled = true;
elements.stopTestBtn.disabled = false;
elements.logContainer.innerHTML = '';
updateProgress(0);
try {
// 1. 常见网站连通信测试前清理缓存
log('═══ 开始第1项:常见网站连通信测试 ═══', 'info');
await clearBrowserCache();
await new Promise(r => setTimeout(r, 500));
await runCommonWebsitesTests();
// 2. 基础网络信息检测前清理缓存
log('═══ 开始第2项:基础网络信息检测 ═══', 'info');
await clearBrowserCache();
await new Promise(r => setTimeout(r, 500));
await runBasicNetworkTests();
// 3. 站点连通性检测前清理缓存
log('═══ 开始第3项:站点连通性检测 ═══', 'info');
await clearBrowserCache();
await new Promise(r => setTimeout(r, 500));
await runSiteConnectivityTests();
// 4. 资源连通性检测前清理缓存
log('═══ 开始第4项:资源连通性检测 ═══', 'info');
await clearBrowserCache();
await new Promise(r => setTimeout(r, 500));
await runResourceTests();
// 5. 网络抖动检测前清理缓存
log('═══ 开始第5项:网络抖动检测 ═══', 'info');
await clearBrowserCache();
await new Promise(r => setTimeout(r, 500));
await runLatencyTests();
// 6. 安全与证书检测前清理缓存
log('═══ 开始第6项:安全与证书检测 ═══', 'info');
await clearBrowserCache();
await new Promise(r => setTimeout(r, 500));
await runSecurityTests();
// 生成总结
generateSummary();
updateProgress(100);
log('✅ 所有检测已完成', 'success');
} catch (error) {
log(`❌ 检测过程中发生错误: ${error.message}`, 'error');
} finally {
isRunning = false;
elements.startTestBtn.disabled = false;
elements.stopTestBtn.disabled = true;
}
}
// 停止检测
function stopTests() {
if (abortController) {
abortController.abort();
}
isRunning = false;
elements.startTestBtn.disabled = false;
elements.stopTestBtn.disabled = true;
log('🛑 检测已手动停止', 'warning');
}
// 清除日志
function clearLog() {
logEntries.length = 0;
elements.logContainer.innerHTML = '';
log('日志已清空', 'info');
}
// 手动解析HTML并检测资源
async function parseHtmlAndDetect() {
const html = elements.htmlInput.value.trim();
if (!html) {
log('请先粘贴HTML源码', 'warning');
return;
}
log('开始手动解析HTML...', 'info');
// 解析HTML获取资源链接
const parsedResources = parseResourcesFromHTML(html, TARGET_URL);
if (parsedResources.length === 0) {
log('未能从HTML中解析到资源链接', 'warning');
return;
}
log(`✓ 从HTML中解析到 ${parsedResources.length} 个资源链接`, 'success');
parsedResources.forEach((res, idx) => {
log(` [${idx + 1}] ${res.name}: ${res.url}`, 'info');
});
// 更新状态
document.getElementById('resourceStatus').className = 'section-status status-info';
document.getElementById('resourceStatus').textContent = '检测中 (手动)';
// 创建资源检测上下文
const requests = new Map();
let totalTime = 0;
const detectStartTime = performance.now();
// 添加首页作为第一个资源
requests.set(TARGET_URL + '/', {
name: '首页',
fullUrl: TARGET_URL + '/',
type: 'document',
initiator: 'manual',
category: 'html',
startTime: 0,
duration: 0,
statusText: '200',
statusClass: 'status-ok',
fromCache: false
});
// 更新表格显示函数
function updateTable() {
const tableBody = document.getElementById('networkTableBody');
tableBody.innerHTML = '';
const requestArray = Array.from(requests.values());
for (const req of requestArray) {
// 瀑布图位置和宽度
const waterfallLeft = (req.startTime / 60000) * 100;
const waterfallWidth = Math.max((req.duration / 60000) * 100, 0.5);
// 时间格式化
const timeText = req.duration > 0 ? Math.round(req.duration) + ' ms' : '-';
const row = document.createElement('tr');
row.innerHTML = `
<td class="col-name" title="${req.fullUrl}">${req.name}</td>
<td class="col-status ${req.statusClass}">${req.statusText}</td>
<td class="col-type">${req.type}</td>
<td class="col-initiator">${req.initiator}</td>
<td class="col-time">${timeText}</td>
<td class="col-waterfall">
<div class="waterfall-cell">
<div class="waterfall-bar ${req.category}"
style="left: ${waterfallLeft}%; width: ${waterfallWidth}%;">
</div>
</div>
</td>
`;
tableBody.appendChild(row);
}
// 更新统计
document.getElementById('networkStats').style.display = 'flex';
document.getElementById('requestCount').textContent = requestArray.length + ' 个请求';
document.getElementById('loadTime').textContent = '完成时间 ' + Math.round(totalTime) + ' ms';
}
updateTable();
// 逐个请求资源
for (let i = 0; i < parsedResources.length; i++) {
const resource = parsedResources[i];
const startTime = performance.now() - detectStartTime;
try {
const requestStartTime = performance.now();
if (resource.category === 'image') {
await new Promise((resolve, reject) => {
const img = new Image();
const timeout = setTimeout(() => reject(new Error('Timeout')), 10000);
img.onload = () => {
clearTimeout(timeout);
resolve();
};
img.onerror = () => {
clearTimeout(timeout);
reject(new Error('Load failed'));
};
img.src = resource.url + '?t=' + Date.now();
});
} else {
const response = await fetchWithTimeout(resource.url, {
method: 'GET',
timeout: 10000
});
if (!response.ok && response.type !== 'opaque' && response.type !== 'opaqueredirect') {
throw new Error('HTTP ' + response.status);
}
}
const endTime = performance.now();
const duration = endTime - requestStartTime;
// 添加到请求列表
const url = resource.url;
if (!requests.has(url)) {
let name = url.split('/').pop() || url;
if (name.length > 40) {
name = name.substring(0, 37) + '...';
}
const req = {
name: name,
fullUrl: url,
type: resource.type,
initiator: 'manual',
category: resource.category,
startTime: startTime,
duration: duration,
statusText: '200',
statusClass: 'status-ok',
fromCache: false
};
requests.set(url, req);
totalTime = Math.max(totalTime, startTime + duration);
updateTable();
}
log(`✓ 成功加载: ${resource.name} (${Math.round(duration)}ms)`, 'success');
} catch (error) {
log(`✗ 加载失败: ${resource.name}`, 'error');
}
// 间隔一点时间
await new Promise(r => setTimeout(r, 200));
}
// 完成
document.getElementById('resourceStatus').className = 'section-status status-success';
document.getElementById('resourceStatus').textContent = '完成';
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'info');
log('资源详情列表:', 'info');
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'info');
const requestArray = Array.from(requests.values());
requestArray.forEach((req, index) => {
const logMsg = `[${index + 1}] 名称: ${req.name} | 类型: ${req.type} | 时间: ${req.duration}ms | URL: ${req.fullUrl}`;
log(logMsg, 'info');
});
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'info');
log(`✓ 手动资源检测完成,共检测 ${requests.size} 个资源`, 'success');
}
// 事件监听
elements.startTestBtn.addEventListener('click', runAllTests);
elements.stopTestBtn.addEventListener('click', stopTests);
elements.clearLogBtn.addEventListener('click', clearLog);
elements.copyLogBtn.addEventListener('click', copyLog);
elements.exportLogBtn.addEventListener('click', exportLog);
// 初始化
window.addEventListener('load', () => {
log('工具已加载完成,点击"开始全面检测"按钮开始诊断', 'info');
});
</script>
</body>
</html>