# SpeedTest · 网速测试服务

基于 **Go + Gin + SQLite (GORM)** 的网页网速测试服务，支持 **延迟、下载、上传** 三项测试，
测速结果自动入库并生成 **排行榜**（下载榜 / 上传榜 / 延迟榜）。

- 线上地址: https://speedtest.lmve.net
- 代码仓库: https://git.lmve.net/dsh/speedtest
- 技术栈: Go 1.25 / Gin / GORM / SQLite (CGO) / TOML 配置 / systemd / Caddy

## 功能特性

- ⚡ **延迟测试**: 前端连续 10 次 ping，取中位数作为延迟、平均绝对偏差作为抖动
- ⬇️ **下载测试**: 固定测速 10 秒——负载根据实测速率自适应（每轮约 2 秒），
  快速连接自动加大负载填满 10 秒，慢速连接用小负载同样 10 秒内完成
- ⬆️ **上传测试**: 同样的固定 10 秒自适应策略
- 🏆 **排行榜**: 单表同时展示延迟 / 下载 / 上传，**点击列头动态排序**（再点切换升降序，
  延迟默认升序、速度/时间默认降序）；**用户列显示地区 + 浏览器**（离线 ip2region 解析地区、
  UA 解析浏览器系统），**不展示任何 IP**；测速完成后**自动高亮"我的成绩"**（含真实名次，
  localStorage 持久化，刷新页面仍高亮）
- 🌐 **IP 处理**: 客户端 IP 仅后台保存（数据库），用于地区解析与调试，**不在排行榜展示**；
  页面上方徽标显示"你的公网 IP · 地区"；服务器直接识别 IP（Caddy `X-Real-IP`），
  无外部 IP 查询依赖
- 📍 **地区解析**: 内置 ip2region v4 离线库（`data/ip2region_v4.xdb`，11MB），纯本地解析
  零外部依赖，加载失败时地区优雅降级为"未知地区"
- 🔒 **Unix socket 监听**: 由 Caddy 反代对外提供 HTTPS，不暴露 TCP 端口

## 项目结构

```
speedtest/
├── main.go                     # 入口：加载配置 → 初始化数据库 → 启动 Web
├── install.sh                  # 一键安装/更新/卸载/管理脚本（systemd）
├── config/
│   ├── config.go               # TOML 配置加载（缺失自动生成/补全）
│   └── defaults.go             # 默认路径与默认值
├── internal/
│   ├── db/
│   │   ├── db.go               # GORM 初始化 + 自动迁移
│   │   └── models.go           # SpeedTestResult 数据模型
│   ├── store/
│   │   ├── stores.go           # 数据访问层聚合
│   │   └── result_store.go     # 测速结果增查（Top N / Count）
│   └── web/
│       ├── server.go           # Gin 引擎 + 路由 + Unix socket/TCP 监听
│       ├── handlers/
│       │   └── speedtest.go    # ping/download/upload/result/rankings
│       └── templates/
│           └── index.html      # 前端单页（原生 JS，无外部依赖）
```

## 本地开发

```bash
# 编译（SQLite 依赖 CGO）
CGO_ENABLED=1 go build -ldflags="-s -w" -o speedtest .

# 本地运行（可用 SPEEDTEST_CONFIG 覆盖配置路径，见 test.toml 示例）
SPEEDTEST_CONFIG=./test.toml ./speedtest
```

## 部署（生产）

```bash
sudo ./install.sh install
```

脚本会自动完成：创建 `speedtest` 系统用户 → 创建目录 → 从 Gitea 拉取代码 →
编译（CGO_ENABLED=1）→ 部署二进制与模板 → 生成 systemd 服务 → 启动并设置开机自启。

| 项目 | 路径 |
| --- | --- |
| 程序目录 | `/opt/speedtest` |
| Unix socket | `/opt/speedtest/web.sock` |
| 配置文件 | `/etc/speedtest/speedtest.toml` |
| SQLite 数据库 | `/srv/speedtest/speedtest.db` |
| 日志 | `journalctl -u speedtest -f` |

### Caddy 反代配置

```caddy
speedtest.lmve.net {
	reverse_proxy unix//opt/speedtest/web.sock {
		header_up X-Real-IP {remote_host}
		header_up Host {host}
	}
}
```

## 配置文件

程序首次启动自动生成 `/etc/speedtest/speedtest.toml`（缺失项自动补全）：

```toml
[database]
  driver = "sqlite"
  dsn = "/srv/speedtest/speedtest.db"

[web]
  addr = "/opt/speedtest/web.sock"   # 以 / 开头为 unix socket，否则为 TCP 端口

[speedtest]
  max_download_bytes = 536870912     # 单次下载请求上限（默认 512 MB）
  max_upload_bytes = 536870912       # 单次上传请求上限（默认 512 MB）
```

> 注：默认上限 512MB 服务于前端自适应测速（快速连接负载会翻倍增长）；
> 旧版本配置中的 100MB 上限可手动调大，或删除配置行后重启让程序补全默认值。

## API

| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/` | 测速页面 |
| GET | `/api/ping` | 延迟探测（返回 `{"pong":true,"ts":...,"client_ip":...,"location":...}`） |
| GET | `/api/download?size=N` | 下载测速，流式返回 N 字节随机数据（默认 10MB，上限 512MB） |
| POST | `/api/upload` | 上传测速，接收请求体并返回 `received/elapsed_s/mbps` |
| POST | `/api/result` | 提交结果 `{latency_ms, jitter_ms, download_mbps, upload_mbps}`（IP/UA 由服务器识别） |
| GET | `/api/rankings?sort=&order=&include_id=` | 排行榜单表：`sort` ∈ download/upload/latency/created，`order` ∈ asc/desc，`include_id` 附加指定记录（真实名次 + `is_mine`）；条目含 `location`/`browser`，**不含 IP** |

客户端 IP 由服务器直接识别（Caddy 注入的 `X-Real-IP`），前端不调用任何外部 IP 服务；
`client_ip` / `server_ip` 字段为 API 兼容保留（可选）。公网 IP 打码保护隐私；
NAT 场景下的内网来源 IP 在排行榜中完整标注，供站长调试区分设备。
