network-speed-js 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +810 -6
- package/dist/network-speed-js.js +167 -0
- package/dist/network-speed-js.umd.js +1 -0
- package/package.json +47 -7
- package/.eslintrc.js +0 -3
- package/.vscode/extensions.json +0 -3
- package/index.html +0 -13
- package/playground/.vscode/extensions.json +0 -3
- package/playground/README.md +0 -18
- package/playground/index.html +0 -13
- package/playground/package.json +0 -20
- package/playground/pnpm-lock.yaml +0 -701
- package/playground/public/vite.svg +0 -1
- package/playground/src/App.vue +0 -30
- package/playground/src/assets/vue.svg +0 -1
- package/playground/src/components/HelloWorld.vue +0 -38
- package/playground/src/main.ts +0 -5
- package/playground/src/style.css +0 -79
- package/playground/src/vite-env.d.ts +0 -1
- package/playground/tsconfig.json +0 -25
- package/playground/tsconfig.node.json +0 -10
- package/playground/vite.config.ts +0 -7
- package/pnpm-lock.yaml +0 -928
- package/src/App.vue +0 -118
- package/src/api.ts +0 -23
- package/src/main.ts +0 -5
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -25
- package/tsconfig.node.json +0 -10
- package/vite.config.ts +0 -7
package/README.md
CHANGED
|
@@ -1,12 +1,816 @@
|
|
|
1
|
+
# Network Speed SDK
|
|
1
2
|
|
|
2
|
-
|
|
3
|
+
<div align="center">
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
一个基于 **Performance API** 的现代化网速测试 SDK,支持内外网自动检测、资源监听和完整的 TypeScript 类型支持。
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
- 使用文档
|
|
7
|
+
**框架无关 · 开箱即用 · 准确可靠**
|
|
8
8
|
|
|
9
|
+
[](https://www.npmjs.com/package/network-speed-js)
|
|
10
|
+
[](https://github.com/Sunny-117/network-speed-js/blob/main/LICENSE)
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
</div>
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🎯 核心亮点
|
|
17
|
+
|
|
18
|
+
### 从 Axios 到 Performance API 的技术升级
|
|
19
|
+
|
|
20
|
+
本项目从 **v0.x(Axios 拦截器方案)** 完全重构为 **v1.0(Performance API 方案)**,实现了测速准确度和功能的质的飞跃。
|
|
21
|
+
|
|
22
|
+
**一句话总结:** 从"JavaScript 层面计时"升级到"浏览器底层性能监控",测速更准确、功能更强大。
|
|
23
|
+
|
|
24
|
+
| 对比项 | 旧方案 (Axios) | 新方案 (Performance API) |
|
|
25
|
+
|--------|---------------|------------------------|
|
|
26
|
+
| 时间测量 | JS 层面(不准确) | 浏览器底层(准确) |
|
|
27
|
+
| 传输大小 | 需手动指定 | 自动获取真实值 |
|
|
28
|
+
| 详细时序 | ❌ 无 | ✅ DNS/TCP/TLS/下载 |
|
|
29
|
+
| 资源监听 | ❌ 不支持 | ✅ 支持所有资源 |
|
|
30
|
+
| 框架依赖 | ❌ 依赖 Axios | ✅ 零依赖 |
|
|
31
|
+
|
|
32
|
+
📖 详细技术对比请查看 [CHANGELOG.md](./CHANGELOG.md)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## ✨ 特性
|
|
37
|
+
|
|
38
|
+
- 🚀 **基于 Performance API** - 使用浏览器原生 API,准确测量真实下载速度
|
|
39
|
+
- 🔄 **内外网自动检测** - 智能切换内网/外网测速资源
|
|
40
|
+
- 📊 **完整的性能数据** - 提供速度、耗时、传输大小等详细信息
|
|
41
|
+
- 🎯 **资源监听** - 实时监听页面资源加载性能
|
|
42
|
+
- 💪 **TypeScript 支持** - 完整的类型定义
|
|
43
|
+
- 🌐 **框架无关** - 可用于 Vue、React、Angular 或原生 JavaScript 项目
|
|
44
|
+
- 📦 **轻量级** - 零依赖,体积小巧
|
|
45
|
+
|
|
46
|
+
## 📦 安装
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install network-speed-js
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 🚀 快速开始
|
|
53
|
+
|
|
54
|
+
### 原生 JavaScript / TypeScript
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { NetworkSpeedSDK } from 'network-speed-js';
|
|
58
|
+
|
|
59
|
+
const sdk = new NetworkSpeedSDK({
|
|
60
|
+
internetUrl: 'https://cdn.example.com/test.bin',
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = await sdk.test();
|
|
64
|
+
console.log(`网速: ${result.speedMbps} Mbps`);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Vue 3
|
|
68
|
+
|
|
69
|
+
```vue
|
|
70
|
+
<template>
|
|
71
|
+
<button @click="testSpeed" :disabled="loading">
|
|
72
|
+
{{ loading ? '测速中...' : '开始测速' }}
|
|
73
|
+
</button>
|
|
74
|
+
<div v-if="result">速度: {{ result.speedMbps }} Mbps</div>
|
|
75
|
+
</template>
|
|
76
|
+
|
|
77
|
+
<script setup>
|
|
78
|
+
import { ref } from 'vue';
|
|
79
|
+
import { NetworkSpeedSDK } from 'network-speed-js';
|
|
80
|
+
|
|
81
|
+
const loading = ref(false);
|
|
82
|
+
const result = ref(null);
|
|
83
|
+
|
|
84
|
+
const testSpeed = async () => {
|
|
85
|
+
loading.value = true;
|
|
86
|
+
const sdk = new NetworkSpeedSDK({
|
|
87
|
+
internetUrl: 'https://cdn.example.com/test.bin',
|
|
88
|
+
});
|
|
89
|
+
result.value = await sdk.test();
|
|
90
|
+
loading.value = false;
|
|
91
|
+
};
|
|
92
|
+
</script>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### React
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { useState } from 'react';
|
|
99
|
+
import { NetworkSpeedSDK } from 'network-speed-js';
|
|
100
|
+
|
|
101
|
+
function SpeedTest() {
|
|
102
|
+
const [loading, setLoading] = useState(false);
|
|
103
|
+
const [result, setResult] = useState(null);
|
|
104
|
+
|
|
105
|
+
const testSpeed = async () => {
|
|
106
|
+
setLoading(true);
|
|
107
|
+
const sdk = new NetworkSpeedSDK({
|
|
108
|
+
internetUrl: 'https://cdn.example.com/test.bin',
|
|
109
|
+
});
|
|
110
|
+
const data = await sdk.test();
|
|
111
|
+
setResult(data);
|
|
112
|
+
setLoading(false);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div>
|
|
117
|
+
<button onClick={testSpeed} disabled={loading}>
|
|
118
|
+
{loading ? '测速中...' : '开始测速'}
|
|
119
|
+
</button>
|
|
120
|
+
{result && <div>速度: {result.speedMbps} Mbps</div>}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Angular
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { Component } from '@angular/core';
|
|
130
|
+
import { NetworkSpeedSDK } from 'network-speed-js';
|
|
131
|
+
|
|
132
|
+
@Component({
|
|
133
|
+
selector: 'app-speed-test',
|
|
134
|
+
template: `
|
|
135
|
+
<button (click)="testSpeed()" [disabled]="loading">
|
|
136
|
+
{{ loading ? '测速中...' : '开始测速' }}
|
|
137
|
+
</button>
|
|
138
|
+
<div *ngIf="result">速度: {{ result.speedMbps }} Mbps</div>
|
|
139
|
+
`
|
|
140
|
+
})
|
|
141
|
+
export class SpeedTestComponent {
|
|
142
|
+
loading = false;
|
|
143
|
+
result: any = null;
|
|
144
|
+
|
|
145
|
+
async testSpeed() {
|
|
146
|
+
this.loading = true;
|
|
147
|
+
const sdk = new NetworkSpeedSDK({
|
|
148
|
+
internetUrl: 'https://cdn.example.com/test.bin',
|
|
149
|
+
});
|
|
150
|
+
this.result = await sdk.test();
|
|
151
|
+
this.loading = false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## 📖 API 文档
|
|
157
|
+
|
|
158
|
+
### NetworkSpeedSDK
|
|
159
|
+
|
|
160
|
+
#### 构造函数
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
new NetworkSpeedSDK(options?: SpeedTestOptions)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**配置选项:**
|
|
167
|
+
|
|
168
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
169
|
+
|------|------|--------|------|
|
|
170
|
+
| `intranetUrl` | `string` | `''` | 内网测速资源URL |
|
|
171
|
+
| `internetUrl` | `string` | `''` | 外网测速资源URL(必填) |
|
|
172
|
+
| `autoDetect` | `boolean` | `true` | 是否自动检测内外网 |
|
|
173
|
+
| `timeout` | `number` | `10000` | 超时时间 (ms) |
|
|
174
|
+
| `thresholds` | `object` | `{fast: 10, medium: 2}` | 网速评估阈值 (Mbps) |
|
|
175
|
+
|
|
176
|
+
#### 方法
|
|
177
|
+
|
|
178
|
+
##### `test(): Promise<SpeedTestResult>`
|
|
179
|
+
|
|
180
|
+
执行网速测试
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const result = await sdk.test();
|
|
184
|
+
// {
|
|
185
|
+
// speedMbps: 45.23,
|
|
186
|
+
// speedKBps: 5653.75,
|
|
187
|
+
// networkType: 'fast',
|
|
188
|
+
// isIntranet: true,
|
|
189
|
+
// duration: 234.56,
|
|
190
|
+
// transferSize: 1323456,
|
|
191
|
+
// resourceUrl: '...'
|
|
192
|
+
// }
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
##### `observeResource(urlPattern: string, callback: Function): () => void`
|
|
196
|
+
|
|
197
|
+
监听特定资源的性能数据
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
const stopObserver = sdk.observeResource('/api/', (entry) => {
|
|
201
|
+
console.log('资源加载:', entry);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// 停止监听
|
|
205
|
+
stopObserver();
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
##### `updateOptions(options: Partial<SpeedTestOptions>): void`
|
|
209
|
+
|
|
210
|
+
更新配置
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
sdk.updateOptions({ timeout: 15000 });
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
##### `destroy(): void`
|
|
217
|
+
|
|
218
|
+
销毁 SDK 实例
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
sdk.destroy();
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### 工具函数
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import {
|
|
228
|
+
getAllResourcesSpeeds,
|
|
229
|
+
calcSpeedByResource,
|
|
230
|
+
evaluateNetworkType,
|
|
231
|
+
} from 'network-speed-js';
|
|
232
|
+
|
|
233
|
+
// 获取所有资源速度
|
|
234
|
+
const speeds = getAllResourcesSpeeds();
|
|
235
|
+
|
|
236
|
+
// 计算单个资源速度
|
|
237
|
+
const entry = performance.getEntriesByType('resource')[0];
|
|
238
|
+
const speed = calcSpeedByResource(entry);
|
|
239
|
+
|
|
240
|
+
// 评估网络类型
|
|
241
|
+
const type = evaluateNetworkType(15.5); // 'fast'
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 类型定义
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
interface SpeedTestResult {
|
|
248
|
+
speedMbps: number; // 下载速度 (Mbps)
|
|
249
|
+
speedKBps: number; // 下载速度 (KB/s)
|
|
250
|
+
networkType: 'fast' | 'medium' | 'slow' | 'unknown';
|
|
251
|
+
isIntranet: boolean; // 是否为内网
|
|
252
|
+
duration: number; // 测试耗时 (ms)
|
|
253
|
+
transferSize: number; // 传输大小 (bytes)
|
|
254
|
+
resourceUrl: string; // 测试资源URL
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
interface ResourceSpeedInfo {
|
|
258
|
+
name: string; // 资源名称
|
|
259
|
+
speedMbps: number; // 下载速度 (Mbps)
|
|
260
|
+
speedKBps: number; // 下载速度 (KB/s)
|
|
261
|
+
downloadTime: number; // 下载时间 (ms)
|
|
262
|
+
transferSize: number; // 传输大小 (bytes)
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## 💡 使用示例
|
|
267
|
+
|
|
268
|
+
### 1. 内外网自动检测
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
const sdk = new NetworkSpeedSDK({
|
|
272
|
+
intranetUrl: 'https://internal-cdn.company.com/test.bin',
|
|
273
|
+
internetUrl: 'https://public-cdn.example.com/test.bin',
|
|
274
|
+
autoDetect: true,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const result = await sdk.test();
|
|
278
|
+
console.log(result.isIntranet ? '内网环境' : '外网环境');
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 2. 首屏加载质量评估
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
const result = await sdk.test();
|
|
285
|
+
|
|
286
|
+
if (result.networkType === 'slow') {
|
|
287
|
+
// 降低图片质量
|
|
288
|
+
// 禁用动画
|
|
289
|
+
// 延迟加载非关键资源
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 3. 动态 CDN 选择
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
const result = await sdk.test();
|
|
297
|
+
const cdnUrl = result.isIntranet
|
|
298
|
+
? 'https://internal-cdn.com'
|
|
299
|
+
: 'https://external-cdn.com';
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### 4. 视频清晰度自适应
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
const result = await sdk.test();
|
|
306
|
+
const quality = result.speedMbps > 10 ? '1080p' :
|
|
307
|
+
result.speedMbps > 5 ? '720p' : '480p';
|
|
308
|
+
videoPlayer.setQuality(quality);
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### 5. 监听 API 请求性能
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
const stopObserver = sdk.observeResource('/api/', (entry) => {
|
|
315
|
+
const downloadTime = entry.responseEnd - entry.responseStart;
|
|
316
|
+
const speed = (entry.transferSize * 8) / downloadTime / 1000;
|
|
317
|
+
console.log(`API 速度: ${speed.toFixed(2)} Mbps`);
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 6. CDN 智能选择
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
class CDNSelector {
|
|
325
|
+
async selectBestCDN() {
|
|
326
|
+
const cdns = [
|
|
327
|
+
'https://cdn-a.example.com/test.bin',
|
|
328
|
+
'https://cdn-b.example.com/test.bin',
|
|
329
|
+
'https://cdn-c.example.com/test.bin',
|
|
330
|
+
];
|
|
331
|
+
|
|
332
|
+
const results = await Promise.all(
|
|
333
|
+
cdns.map(async (url) => {
|
|
334
|
+
const sdk = new NetworkSpeedSDK({ internetUrl: url });
|
|
335
|
+
try {
|
|
336
|
+
const result = await sdk.test();
|
|
337
|
+
return { url, speed: result.speedMbps };
|
|
338
|
+
} catch {
|
|
339
|
+
return { url, speed: 0 };
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
return results.reduce((best, curr) =>
|
|
345
|
+
curr.speed > best.speed ? curr : best
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### 7. 网速监控和告警
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
class NetworkMonitor {
|
|
355
|
+
private sdk: NetworkSpeedSDK;
|
|
356
|
+
private threshold = 2; // Mbps
|
|
357
|
+
|
|
358
|
+
constructor() {
|
|
359
|
+
this.sdk = new NetworkSpeedSDK({
|
|
360
|
+
internetUrl: 'https://cdn.example.com/test.bin',
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async monitor() {
|
|
365
|
+
const result = await this.sdk.test();
|
|
366
|
+
|
|
367
|
+
if (result.speedMbps < this.threshold) {
|
|
368
|
+
this.alert('网速过慢', result);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return result;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private alert(message: string, result: SpeedTestResult) {
|
|
375
|
+
console.warn(message, result);
|
|
376
|
+
// 发送告警通知、记录日志、降级处理
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const monitor = new NetworkMonitor();
|
|
381
|
+
setInterval(() => monitor.monitor(), 60000); // 每分钟检测
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## 🎨 框架集成
|
|
385
|
+
|
|
386
|
+
### Vue 3 自定义 Hook
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// useNetworkSpeed.ts
|
|
390
|
+
import { ref, onUnmounted } from 'vue';
|
|
391
|
+
import { NetworkSpeedSDK } from 'network-speed-js';
|
|
392
|
+
|
|
393
|
+
export function useNetworkSpeed(options = {}) {
|
|
394
|
+
const isLoading = ref(false);
|
|
395
|
+
const result = ref(null);
|
|
396
|
+
const error = ref(null);
|
|
397
|
+
const sdk = new NetworkSpeedSDK(options);
|
|
398
|
+
|
|
399
|
+
const test = async () => {
|
|
400
|
+
isLoading.value = true;
|
|
401
|
+
error.value = null;
|
|
402
|
+
try {
|
|
403
|
+
result.value = await sdk.test();
|
|
404
|
+
} catch (err) {
|
|
405
|
+
error.value = err;
|
|
406
|
+
} finally {
|
|
407
|
+
isLoading.value = false;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
onUnmounted(() => sdk.destroy());
|
|
412
|
+
|
|
413
|
+
return { isLoading, result, error, test };
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
使用:
|
|
418
|
+
|
|
419
|
+
```vue
|
|
420
|
+
<script setup>
|
|
421
|
+
import { useNetworkSpeed } from './useNetworkSpeed';
|
|
422
|
+
|
|
423
|
+
const { isLoading, result, error, test } = useNetworkSpeed({
|
|
424
|
+
internetUrl: 'https://cdn.example.com/test.bin',
|
|
425
|
+
});
|
|
426
|
+
</script>
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### React 自定义 Hook
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
// useNetworkSpeed.ts
|
|
433
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
434
|
+
import { NetworkSpeedSDK } from 'network-speed-js';
|
|
435
|
+
|
|
436
|
+
export function useNetworkSpeed(options = {}) {
|
|
437
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
438
|
+
const [result, setResult] = useState(null);
|
|
439
|
+
const [error, setError] = useState(null);
|
|
440
|
+
const [sdk] = useState(() => new NetworkSpeedSDK(options));
|
|
441
|
+
|
|
442
|
+
const test = useCallback(async () => {
|
|
443
|
+
setIsLoading(true);
|
|
444
|
+
setError(null);
|
|
445
|
+
try {
|
|
446
|
+
const testResult = await sdk.test();
|
|
447
|
+
setResult(testResult);
|
|
448
|
+
} catch (err) {
|
|
449
|
+
setError(err);
|
|
450
|
+
} finally {
|
|
451
|
+
setIsLoading(false);
|
|
452
|
+
}
|
|
453
|
+
}, [sdk]);
|
|
454
|
+
|
|
455
|
+
useEffect(() => () => sdk.destroy(), [sdk]);
|
|
456
|
+
|
|
457
|
+
return { isLoading, result, error, test };
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## ⚙️ 配置指南
|
|
462
|
+
|
|
463
|
+
### 测速资源准备
|
|
464
|
+
|
|
465
|
+
#### 服务端配置(Nginx)
|
|
466
|
+
|
|
467
|
+
```nginx
|
|
468
|
+
location /speed-test.bin {
|
|
469
|
+
# 禁用缓存
|
|
470
|
+
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
|
471
|
+
add_header Pragma "no-cache";
|
|
472
|
+
add_header Expires "0";
|
|
473
|
+
|
|
474
|
+
# 设置内容类型
|
|
475
|
+
add_header Content-Type "application/octet-stream";
|
|
476
|
+
|
|
477
|
+
# 启用 CORS
|
|
478
|
+
add_header Access-Control-Allow-Origin "*";
|
|
479
|
+
add_header Access-Control-Allow-Methods "GET, OPTIONS";
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### 生成测速文件
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
# 生成 500KB 的随机文件
|
|
487
|
+
dd if=/dev/urandom of=speed-test.bin bs=1024 count=500
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**测速文件建议:**
|
|
491
|
+
- 文件大小:200KB ~ 1MB
|
|
492
|
+
- 禁用缓存
|
|
493
|
+
- 启用 CORS
|
|
494
|
+
- 使用 CDN 分发
|
|
495
|
+
|
|
496
|
+
### 配置项详解
|
|
497
|
+
|
|
498
|
+
#### intranetUrl
|
|
499
|
+
|
|
500
|
+
内网测速资源URL。如果配置了此项且 `autoDetect` 为 true,会优先尝试内网测速。
|
|
501
|
+
|
|
502
|
+
**建议:**
|
|
503
|
+
- 使用公司内部 CDN 资源
|
|
504
|
+
- 文件大小 200KB ~ 1MB
|
|
505
|
+
- 确保资源稳定可访问
|
|
506
|
+
|
|
507
|
+
#### internetUrl
|
|
508
|
+
|
|
509
|
+
外网测速资源URL。必填项。
|
|
510
|
+
|
|
511
|
+
**建议:**
|
|
512
|
+
- 使用公共 CDN 资源
|
|
513
|
+
- 文件大小 200KB ~ 1MB
|
|
514
|
+
- 选择地理位置接近用户的 CDN
|
|
515
|
+
|
|
516
|
+
#### timeout
|
|
517
|
+
|
|
518
|
+
单次测速的超时时间(毫秒)。
|
|
519
|
+
|
|
520
|
+
**建议值:**
|
|
521
|
+
- 快速网络:5000ms
|
|
522
|
+
- 一般网络:10000ms
|
|
523
|
+
- 慢速网络:15000ms
|
|
524
|
+
|
|
525
|
+
#### thresholds
|
|
526
|
+
|
|
527
|
+
网速评估阈值,用于判断网络类型。
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
{
|
|
531
|
+
fast: 10, // >= 10 Mbps 为 'fast'
|
|
532
|
+
medium: 2, // >= 2 Mbps 为 'medium'
|
|
533
|
+
// < 2 Mbps 为 'slow'
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
## 🔍 核心概念
|
|
538
|
+
|
|
539
|
+
### Performance API 是什么?
|
|
540
|
+
|
|
541
|
+
Performance API 是浏览器提供的原生性能监控接口,可以精确测量资源加载的各个阶段耗时。
|
|
542
|
+
|
|
543
|
+
### 测速原理
|
|
544
|
+
|
|
545
|
+
```
|
|
546
|
+
已知资源大小 ÷ 实际下载时间 = 当前有效下载速率
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
关键时间点:
|
|
550
|
+
- `responseStart`: 开始接收首字节(TTFB 结束)
|
|
551
|
+
- `responseEnd`: 资源下载完成
|
|
552
|
+
- `transferSize`: 实际网络传输字节数(含 header)
|
|
553
|
+
|
|
554
|
+
### 内外网检测原理
|
|
555
|
+
|
|
556
|
+
1. 先尝试请求内网资源
|
|
557
|
+
2. 如果内网资源超时或失败,自动切换到外网资源
|
|
558
|
+
3. 根据成功的资源判断当前网络环境
|
|
559
|
+
|
|
560
|
+
## 📊 Performance API vs 其他方案
|
|
561
|
+
|
|
562
|
+
| 方案 | 准确度 | 可控性 | 复杂度 | 推荐 |
|
|
563
|
+
|------|--------|--------|--------|------|
|
|
564
|
+
| Performance API | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ✅ 主力 |
|
|
565
|
+
| Axios 拦截器 | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | 旧方案 |
|
|
566
|
+
| Network Info API | ⭐⭐ | ⭐ | ⭐ | 辅助 |
|
|
567
|
+
| WebRTC 测速 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | 重量级 |
|
|
568
|
+
|
|
569
|
+
### 为什么从 Axios 升级到 Performance API?
|
|
570
|
+
|
|
571
|
+
**旧方案(Axios 拦截器)的局限:**
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
// ❌ 旧方案问题
|
|
575
|
+
const start = Date.now();
|
|
576
|
+
await axios.get(url);
|
|
577
|
+
const end = Date.now();
|
|
578
|
+
const time = end - start; // 包含 JS 执行时间,不准确
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
- ❌ 只能测量 JavaScript 层面时间
|
|
582
|
+
- ❌ 无法获取真实网络传输大小
|
|
583
|
+
- ❌ 受浏览器缓存影响
|
|
584
|
+
- ❌ 需要手动指定文件大小
|
|
585
|
+
|
|
586
|
+
**新方案(Performance API)的优势:**
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
// ✅ 新方案优势
|
|
590
|
+
const entry = performance.getEntriesByType('resource')[0];
|
|
591
|
+
const time = entry.responseEnd - entry.responseStart; // 纯网络时间
|
|
592
|
+
const size = entry.transferSize; // 真实传输字节数
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
- ✅ 浏览器底层 API,准确可靠
|
|
596
|
+
- ✅ 自动获取真实传输大小
|
|
597
|
+
- ✅ 可识别缓存命中
|
|
598
|
+
- ✅ 提供完整的加载时序
|
|
599
|
+
|
|
600
|
+
## ❓ 常见问题
|
|
601
|
+
|
|
602
|
+
### Q1: 测速结果不准确?
|
|
603
|
+
|
|
604
|
+
**可能原因:**
|
|
605
|
+
1. 测速文件太小(< 100KB)
|
|
606
|
+
2. 测速文件被缓存
|
|
607
|
+
3. CDN 未命中,回源慢
|
|
608
|
+
|
|
609
|
+
**解决方案:**
|
|
610
|
+
```typescript
|
|
611
|
+
const sdk = new NetworkSpeedSDK({
|
|
612
|
+
internetUrl: 'https://cdn.example.com/test-500kb.bin',
|
|
613
|
+
timeout: 15000, // 增加超时时间
|
|
614
|
+
});
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### Q2: 如何区分内外网?
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
const sdk = new NetworkSpeedSDK({
|
|
621
|
+
intranetUrl: 'https://internal.company.com/test.bin', // 只有内网能访问
|
|
622
|
+
internetUrl: 'https://public-cdn.com/test.bin', // 公网可访问
|
|
623
|
+
autoDetect: true,
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
const result = await sdk.test();
|
|
627
|
+
console.log(result.isIntranet ? '内网' : '外网');
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Q3: 外网 CDN 超时怎么办?
|
|
631
|
+
|
|
632
|
+
```typescript
|
|
633
|
+
// 方案1: 增加超时时间
|
|
634
|
+
const sdk = new NetworkSpeedSDK({
|
|
635
|
+
internetUrl: 'https://cdn.example.com/test.bin',
|
|
636
|
+
timeout: 20000, // 20秒
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
// 方案2: 使用多个 CDN 备份
|
|
640
|
+
async function testWithFallback() {
|
|
641
|
+
const cdns = [
|
|
642
|
+
'https://cdn1.example.com/test.bin',
|
|
643
|
+
'https://cdn2.example.com/test.bin',
|
|
644
|
+
];
|
|
645
|
+
|
|
646
|
+
for (const url of cdns) {
|
|
647
|
+
try {
|
|
648
|
+
const sdk = new NetworkSpeedSDK({ internetUrl: url });
|
|
649
|
+
return await sdk.test();
|
|
650
|
+
} catch (error) {
|
|
651
|
+
console.warn(`CDN ${url} 失败,尝试下一个`);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
throw new Error('所有 CDN 都不可用');
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### Q4: 移动端支持吗?
|
|
659
|
+
|
|
660
|
+
完全支持。Performance API 在现代移动浏览器中都可用。
|
|
661
|
+
|
|
662
|
+
**注意事项:**
|
|
663
|
+
- 移动网络波动大,建议多次测试取平均值
|
|
664
|
+
- 注意流量消耗
|
|
665
|
+
- 考虑 WiFi 和移动网络的切换
|
|
666
|
+
|
|
667
|
+
```typescript
|
|
668
|
+
// 检测网络类型
|
|
669
|
+
const connection = navigator.connection;
|
|
670
|
+
if (connection) {
|
|
671
|
+
console.log('网络类型:', connection.effectiveType);
|
|
672
|
+
console.log('下行速度估算:', connection.downlink, 'Mbps');
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
## 🚀 性能优化
|
|
677
|
+
|
|
678
|
+
### 1. 避免频繁测速
|
|
679
|
+
|
|
680
|
+
```typescript
|
|
681
|
+
// ❌ 不好的做法
|
|
682
|
+
setInterval(() => sdk.test(), 1000); // 每秒测速,浪费带宽
|
|
683
|
+
|
|
684
|
+
// ✅ 好的做法
|
|
685
|
+
let lastTestTime = 0;
|
|
686
|
+
const MIN_INTERVAL = 60000; // 最小间隔 1 分钟
|
|
687
|
+
|
|
688
|
+
async function testIfNeeded() {
|
|
689
|
+
const now = Date.now();
|
|
690
|
+
if (now - lastTestTime < MIN_INTERVAL) return;
|
|
691
|
+
|
|
692
|
+
lastTestTime = now;
|
|
693
|
+
await sdk.test();
|
|
694
|
+
}
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### 2. 缓存测速结果
|
|
698
|
+
|
|
699
|
+
```typescript
|
|
700
|
+
class SpeedCache {
|
|
701
|
+
private cache = null;
|
|
702
|
+
private cacheTime = 0;
|
|
703
|
+
private cacheDuration = 5 * 60 * 1000; // 5 分钟
|
|
704
|
+
|
|
705
|
+
async getSpeed(sdk) {
|
|
706
|
+
const now = Date.now();
|
|
707
|
+
if (this.cache && now - this.cacheTime < this.cacheDuration) {
|
|
708
|
+
return this.cache;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
this.cache = await sdk.test();
|
|
712
|
+
this.cacheTime = now;
|
|
713
|
+
return this.cache;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
### 3. 错误处理
|
|
719
|
+
|
|
720
|
+
```typescript
|
|
721
|
+
async function testSpeed() {
|
|
722
|
+
const sdk = new NetworkSpeedSDK({
|
|
723
|
+
internetUrl: 'https://cdn.example.com/test.bin',
|
|
724
|
+
timeout: 10000,
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
try {
|
|
728
|
+
const result = await sdk.test();
|
|
729
|
+
return result;
|
|
730
|
+
} catch (error) {
|
|
731
|
+
if (error.message.includes('超时')) {
|
|
732
|
+
console.warn('测速超时,网络可能较慢');
|
|
733
|
+
} else if (error.message.includes('加载失败')) {
|
|
734
|
+
console.error('测速资源不可用');
|
|
735
|
+
}
|
|
736
|
+
throw error;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
## ⚠️ 注意事项
|
|
742
|
+
|
|
743
|
+
### Performance API 能做什么
|
|
744
|
+
|
|
745
|
+
✅ 基于真实资源加载评估下载速度
|
|
746
|
+
✅ 获取详细的资源加载时序
|
|
747
|
+
✅ 监听页面所有资源性能
|
|
748
|
+
✅ 支持自定义测速资源
|
|
749
|
+
|
|
750
|
+
### Performance API 不能做什么
|
|
751
|
+
|
|
752
|
+
❌ 测量上行速度(upload)
|
|
753
|
+
❌ 测量丢包率
|
|
754
|
+
❌ 测量 RTT 抖动
|
|
755
|
+
❌ 脱离真实请求独立测速
|
|
756
|
+
|
|
757
|
+
## 🤝 贡献
|
|
758
|
+
|
|
759
|
+
欢迎提交 Issue 和 Pull Request!
|
|
760
|
+
|
|
761
|
+
### 本地开发
|
|
762
|
+
|
|
763
|
+
```bash
|
|
764
|
+
# 克隆项目
|
|
765
|
+
git clone https://github.com/Sunny-117/network-speed-js.git
|
|
766
|
+
cd network-speed-js
|
|
767
|
+
|
|
768
|
+
# 安装依赖
|
|
769
|
+
npm install
|
|
770
|
+
|
|
771
|
+
# 启动开发服务器
|
|
772
|
+
npm run dev
|
|
773
|
+
|
|
774
|
+
# 构建
|
|
775
|
+
npm run build
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
### 项目结构
|
|
779
|
+
|
|
780
|
+
```
|
|
781
|
+
network-speed-js/
|
|
782
|
+
├── src/
|
|
783
|
+
│ ├── core/ # 核心功能
|
|
784
|
+
│ │ ├── speed-tester.ts
|
|
785
|
+
│ │ └── performance-utils.ts
|
|
786
|
+
│ ├── components/ # Vue 组件(可选)
|
|
787
|
+
│ ├── types.ts # TypeScript 类型定义
|
|
788
|
+
│ ├── sdk.ts # SDK 主入口
|
|
789
|
+
│ ├── index.ts # 导出入口
|
|
790
|
+
│ ├── App.vue # Demo 示例
|
|
791
|
+
│ └── main.ts # 应用入口
|
|
792
|
+
├── dist/ # 构建输出
|
|
793
|
+
├── CHANGELOG.md # 更新日志
|
|
794
|
+
└── README.md # 项目文档
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
## 📄 License
|
|
798
|
+
|
|
799
|
+
MIT License
|
|
800
|
+
|
|
801
|
+
## 🔗 相关链接
|
|
802
|
+
|
|
803
|
+
- [更新日志 (CHANGELOG)](./CHANGELOG.md)
|
|
804
|
+
- [Performance API 文档](https://developer.mozilla.org/en-US/docs/Web/API/Performance_API)
|
|
805
|
+
- [PerformanceResourceTiming](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming)
|
|
806
|
+
- [Network Information API](https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API)
|
|
807
|
+
|
|
808
|
+
---
|
|
809
|
+
|
|
810
|
+
<div align="center">
|
|
811
|
+
|
|
812
|
+
Made with ❤️ by [Sunny-117](https://github.com/Sunny-117)
|
|
813
|
+
|
|
814
|
+
如果这个项目对你有帮助,请给个 ⭐️ Star 支持一下!
|
|
815
|
+
|
|
816
|
+
</div>
|