# 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> ▼ 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>` 包裹的共享快照,可廉价 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://:8080") }; var resp = await http.GetStringAsync("/api/client/health"); // => {"status":"ok"} ``` 后续把客户端要的功能加到 `routes/client.rs` 即可,与前端接口互不影响。