160 lines
6.3 KiB
Markdown
160 lines
6.3 KiB
Markdown
|
|
# InternetProject 后端(Rust)
|
|||
|
|
|
|||
|
|
系统监控后端:每秒采集 CPU 使用率、内存使用率、内存总量与可用内存,
|
|||
|
|
格式化成字符串,通过 HTTP 接口提供给 **React 前端**(顶部栏每秒刷新展示)。
|
|||
|
|
接口按使用方分区,并为未来的 **.NET 客户端**预留了独立区域。
|
|||
|
|
|
|||
|
|
技术栈:`axum` 0.8 + `tokio` + `sysinfo` + `serde` + `tower-http`(cors)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. 整体架构与数据流
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────────────────────────────┐
|
|||
|
|
每秒采样 │ 后台任务 run_sampler (tokio) │
|
|||
|
|
┌────────────┐ write │ sysinfo::System 刷新指标 │
|
|||
|
|
│ sysinfo │───────►│ → Stats → 写入共享快照 │
|
|||
|
|
└────────────┘ └──────────────┬───────────────┘
|
|||
|
|
│ Arc<RwLock<Stats>>
|
|||
|
|
▼ read
|
|||
|
|
浏览器(React) GET /api/web/stats ┌─────────────┐
|
|||
|
|
localhost:5173 ───────────────────►│ axum 路由 │ CORS 放行前端源
|
|||
|
|
◄─── JSON(Stats) ────│ 0.0.0.0:8080│
|
|||
|
|
└─────────────┘
|
|||
|
|
.NET 客户端 GET /api/client/* ───► 同一服务,不同接口区(无 CORS)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**关键设计**:采样(写)与请求(读)解耦。后台任务持有 `sysinfo::System`
|
|||
|
|
每秒刷新一次并写入一个 `RwLock` 保护的快照;HTTP handler 只读快照、
|
|||
|
|
立即返回,不会因为采集系统指标而阻塞请求。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 逐文件说明(`src/`)
|
|||
|
|
|
|||
|
|
| 文件 | 职责 |
|
|||
|
|
|------|------|
|
|||
|
|
| `main.rs` | 入口。建 `AppState` → `tokio::spawn` 采样任务 → 构建路由 → 绑定 `0.0.0.0:8080` → `axum::serve`。 |
|
|||
|
|
| `monitor.rs` | `Stats` 数据结构、格式化、后台采样循环 `run_sampler`。 |
|
|||
|
|
| `state.rs` | `AppState`:被 `Arc<RwLock<…>>` 包裹的共享快照,可廉价 clone。 |
|
|||
|
|
| `routes/mod.rs` | 组装并合并 `web` / `client` 两个子路由。 |
|
|||
|
|
| `routes/web.rs` | 前端接口 `GET /api/web/stats`,**挂 CORS**。 |
|
|||
|
|
| `routes/client.rs` | 未来 .NET 客户端接口区,目前 `GET /api/client/health` 占位,**不挂 CORS**。 |
|
|||
|
|
|
|||
|
|
### `monitor.rs` 要点
|
|||
|
|
|
|||
|
|
- **`Stats`**:`#[serde(rename_all = "camelCase")]`,序列化为
|
|||
|
|
`text` / `cpu` / `memUsedPct` / `memTotalBytes` / `memAvailableBytes`。
|
|||
|
|
`text` 是给 UI 直接展示的字符串,其余原始字段留给后续可视化。
|
|||
|
|
- **CPU 使用率需要两次刷新**:`sysinfo` 的 CPU 占用率是两次刷新之间的差值。
|
|||
|
|
所以 `run_sampler` 先 `refresh_cpu_usage()` 预热一次,等待
|
|||
|
|
`MINIMUM_CPU_UPDATE_INTERVAL`,之后再进入「每秒刷新」循环,读数才准确。
|
|||
|
|
- **内存单位**:`sysinfo` 返回**字节**。`used = total - available`,
|
|||
|
|
百分比 = `used / total * 100`,展示时换算 GB。
|
|||
|
|
|
|||
|
|
### `state.rs` 要点
|
|||
|
|
|
|||
|
|
- `Arc` 让状态在「采样任务」和「每个请求」之间共享;
|
|||
|
|
`RwLock` 允许多读单写(这里是单写者 + 偶发读者)。
|
|||
|
|
- handler 里 `state.snapshot.read().await.clone()` 取一份快照副本后立刻释放锁。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. CORS:为什么前端要、客户端不要
|
|||
|
|
|
|||
|
|
**CORS(跨源资源共享)是浏览器的安全机制**,由浏览器强制执行:
|
|||
|
|
|
|||
|
|
- React 前端运行在浏览器里,页面源是 `http://localhost:5173`,
|
|||
|
|
而后端是 `http://localhost:8080` —— **协议/host/port 任一不同即为「跨源」**。
|
|||
|
|
浏览器默认禁止 JS 读取跨源响应,除非后端在响应里带上
|
|||
|
|
`Access-Control-Allow-Origin` 等头声明「我允许这个源」。
|
|||
|
|
所以 `web.rs` 用 `tower-http` 的 `CorsLayer` 放行前端源:
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
let cors = CorsLayer::new()
|
|||
|
|
.allow_origin(AllowOrigin::list([
|
|||
|
|
"http://localhost:5173".parse().unwrap(),
|
|||
|
|
"http://127.0.0.1:5173".parse().unwrap(),
|
|||
|
|
]))
|
|||
|
|
.allow_methods([Method::GET]);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **.NET 客户端不是浏览器**(用 `HttpClient` 直接发请求),
|
|||
|
|
不存在「同源策略」,也不会发 CORS 预检、不校验响应头。
|
|||
|
|
因此 `client.rs` **不挂 CORS** —— 加了也无意义。
|
|||
|
|
|
|||
|
|
> 一句话:CORS 只拦浏览器。给前端配,给客户端不用配。
|
|||
|
|
|
|||
|
|
部署到生产、前端由后端托管成同源时,CORS 也不会碍事;若前端换了域名,
|
|||
|
|
只需在 `allow_origin` 列表里加上新源。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 接口分区(前端 vs 客户端不共用接口)
|
|||
|
|
|
|||
|
|
两类调用方走**不同的路径前缀**,在代码里也是不同的子路由模块,便于各自演进、
|
|||
|
|
施加不同中间件(如 CORS、认证):
|
|||
|
|
|
|||
|
|
| 区域 | 前缀 | 使用方 | CORS |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| Web | `/api/web/*` | React 前端(浏览器) | ✅ 放行前端源 |
|
|||
|
|
| Client | `/api/client/*` | 未来 .NET 客户端 | ❌ 不需要 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. API 参考
|
|||
|
|
|
|||
|
|
### `GET /api/web/stats` (前端)
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"text": "CPU 4.4% · MEM 48.3% · 15.2/31.4 GB · Free 16.2 GB",
|
|||
|
|
"cpu": 4.39,
|
|||
|
|
"memUsedPct": 48.35,
|
|||
|
|
"memTotalBytes": 33664409600,
|
|||
|
|
"memAvailableBytes": 17387958272
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### `GET /api/client/health` (客户端占位)
|
|||
|
|
```json
|
|||
|
|
{ "status": "ok" }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. 运行
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 拉依赖若慢,先给 cargo 设代理:
|
|||
|
|
# export http_proxy=http://127.0.0.1:7890 https_proxy=http://127.0.0.1:7890
|
|||
|
|
|
|||
|
|
cd Server
|
|||
|
|
cargo run # 监听 http://0.0.0.0:8080
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
快速自测:
|
|||
|
|
```bash
|
|||
|
|
curl http://localhost:8080/api/web/stats
|
|||
|
|
curl http://localhost:8080/api/client/health
|
|||
|
|
# 验证 CORS 头:
|
|||
|
|
curl -i -H "Origin: http://localhost:5173" http://localhost:8080/api/web/stats | grep -i access-control
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
前端:`cd Client && npm run dev`,浏览器开 `http://localhost:5173`,
|
|||
|
|
顶部栏会每秒刷新系统状态字符串(后端没开时回退到默认文案、状态点变暗)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. 未来 .NET 客户端如何连接
|
|||
|
|
|
|||
|
|
客户端走 `/api/client/*`,不受 CORS 影响,直接请求即可:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
using var http = new HttpClient { BaseAddress = new Uri("http://<server-host>:8080") };
|
|||
|
|
var resp = await http.GetStringAsync("/api/client/health");
|
|||
|
|
// => {"status":"ok"}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
后续把客户端要的功能加到 `routes/client.rs` 即可,与前端接口互不影响。
|