PersonalWebApplication/Server/README.md
cc 2205408252 Initial commit: React client + Rust server full-stack project
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-17 20:49:17 +08:00

160 lines
6.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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