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` 即可,与前端接口互不影响。
|