- PhotoWallPage: 仅当原文件 >1MB 才 Canvas 压缩,小图原样上传 - auth.rs: seed 兜底表新增 UserTest6340(本地无 DB 时用;线上走 wall_users 表) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
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放行前端源: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 即可,与前端接口互不影响。