PersonalWebApplication/Server
2026-07-09 23:55:17 +08:00
..
src feat: add folder media support 2026-07-09 23:55:17 +08:00
.gitignore feat(client): 隐藏登录入口改为后端校验 URL hash 2026-06-20 20:45:30 +08:00
Cargo.lock feat: add folder media support 2026-07-09 23:55:17 +08:00
Cargo.toml feat: add folder media support 2026-07-09 23:55:17 +08:00
README.md Initial commit: React client + Rust server full-stack project 2026-06-17 20:49:17 +08:00

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 入口。建 AppStatetokio::spawn 采样任务 → 构建路由 → 绑定 0.0.0.0:8080axum::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 / memAvailableBytestext 是给 UI 直接展示的字符串,其余原始字段留给后续可视化。
  • CPU 使用率需要两次刷新sysinfo 的 CPU 占用率是两次刷新之间的差值。 所以 run_samplerrefresh_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.rstower-httpCorsLayer 放行前端源:

    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 (前端)

{
  "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 (客户端占位)

{ "status": "ok" }

6. 运行

# 拉依赖若慢,先给 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

快速自测:

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 影响,直接请求即可:

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