5 分钟搭好开发环境:Rust + Axum 项目从零开始

摘要: 从 Rust 工具链安装到启动第一个 Axum Web 服务,手把手教学。涵盖 Cargo.toml 依赖配置、Router/Extractor/Middleware/State 四大核心概念、项目分层架构设计、cargo-watch 热加载开发,附完整可运行代码。


一、安装 Rust 全家桶

Linux / macOS

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env

Windows

下载 rustup-init.exe,一路 Next。

验证安装

$ rustc --version
rustc 1.85.0 (4d91de4c4 2025-02-17)

$ cargo --version
cargo 1.85.0 (d73d2caf9 2025-01-15)

配置国内镜像(中国大陆用户)

编辑 ~/.cargo/config.toml

[source.crates-io]
replace-with = 'ustc'

[source.ustc]
registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"

IDE 推荐

  • VS Code + rust-analyzer 插件(免费,首选)
  • RustRover(JetBrains,功能最强)
  • Zed(新一代高性能编辑器)

二、项目初始化

cargo new nexus-ops
cd nexus-ops

生成的结构:

nexus-ops/
├── Cargo.toml      # 项目元数据 + 依赖
└── src/
    └── main.rs     # 入口点

编辑 Cargo.toml

[package]
name = "nexus-ops"
version = "0.1.0"
edition = "2021"

[dependencies]
# Web 框架
axum = "0.7"
# 异步运行时
tokio = { version = "1", features = ["full"] }
# JSON 序列化
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# 日志
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# 静态文件 + CORS
tower-http = { version = "0.5", features = ["cors", "fs"] }

三、第一个 Axum 应用

// src/main.rs
use axum::{
    Router,
    routing::get,
    response::Json,
    extract::Path,
};
use serde::Serialize;
use std::net::SocketAddr;
use tower_http::services::ServeDir;
use tracing_subscriber;

#[derive(Serialize)]
struct ApiResponse<T: Serialize> {
    success: bool,
    data: Option<T>,
    error: Option<String>,
}

#[derive(Serialize)]
struct HealthInfo {
    status: String,
    version: String,
    uptime_seconds: u64,
}

/// GET /api/health — 健康检查
async fn health_check() -> Json<ApiResponse<HealthInfo>> {
    Json(ApiResponse {
        success: true,
        data: Some(HealthInfo {
            status: "healthy".into(),
            version: env!("CARGO_PKG_VERSION").into(),
            uptime_seconds: 0,
        }),
        error: None,
    })
}

/// GET /api/devices/:id — 获取单个设备
async fn get_device(Path(id): Path<i64>) -> Json<ApiResponse<String>> {
    Json(ApiResponse {
        success: true,
        data: Some(format!("Device #{}", id)),
        error: None,
    })
}

/// GET / — 首页重定向
async fn index() -> &'static str {
    "NexusOps API Server is running.\n"
}

#[tokio::main]
async fn main() {
    // 初始化日志
    tracing_subscriber::fmt()
        .with_env_filter("nexus_ops=info,tower_http=info")
        .init();

    // 构建路由
    let app = Router::new()
        .route("/", get(index))
        .route("/api/health", get(health_check))
        .route("/api/devices/:id", get(get_device))
        .nest_service("/static", ServeDir::new("static"))
        .fallback(|| async { "404 Not Found" });

    // 绑定并启动
    let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
    tracing::info!("NexusOps server listening on http://{}", addr);

    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

运行:

cargo run

# 输出:
# 2026-05-07T10:00:00.000Z  INFO nexus_ops: NexusOps server listening on http://0.0.0.0:8080

测试:

# 健康检查
$ curl http://localhost:8080/api/health | jq
{
  "success": true,
  "data": {
    "status": "healthy",
    "version": "0.1.0",
    "uptime_seconds": 0
  },
  "error": null
}

# 路径参数
$ curl http://localhost:8080/api/devices/42
{"success":true,"data":"Device #42","error":null}

四、Axum 四大核心概念

1. Router(路由器)

let app = Router::new()
    .route("/api/health", get(health_check))        // GET 请求
    .route("/api/devices", post(create_device))     // POST 请求
    .route("/api/devices/:id", get(get_device)      // 路径参数
        .delete(delete_device))                      // 链式组合
    .nest("/api/v2", v2_router)                      // 嵌套路由
    .merge(another_router)                            // 合并路由
    .fallback(fallback_handler);                      // 404 处理

2. Extractor(提取器)

use axum::{
    extract::{Path, Query, State, Json},
    http::StatusCode,
};

// 路径参数: /api/devices/42
async fn handler(Path(id): Path<i64>) {}

// 查询参数: /api/devices?page=1&size=20
#[derive(Deserialize)]
struct Pagination { page: Option<i64>, size: Option<i64> }
async fn handler(Query(p): Query<Pagination>) {}

// JSON 请求体
#[derive(Deserialize)]
struct CreateDeviceReq { name: String, ip: String }
async fn handler(Json(req): Json<CreateDeviceReq>) {}

// 应用状态
async fn handler(State(state): State<Arc<AppState>>) {}

// 组合使用
async fn create_device(
    State(state): State<Arc<AppState>>,
    Json(req): Json<CreateDeviceReq>,
) -> Result<Json<Device>, StatusCode> {
    // 验证、存储、返回
}

3. State(共享状态)

use std::sync::Arc;
use rusqlite::Connection;

pub struct AppState {
    pub db: Connection,
    // 后面会加入更多: plugin_registry, alert_engine, ...
}

#[tokio::main]
async fn main() {
    let state = Arc::new(AppState {
        db: init_database(),
    });

    let app = Router::new()
        .route("/api/devices", get(list_devices))
        .with_state(state);

    // ...
}

// 在 handler 中访问数据库
async fn list_devices(
    State(state): State<Arc<AppState>>,
) -> Json<Vec<Device>> {
    let devices = DeviceRepo::list(&state.db);
    Json(devices)
}

4. Middleware(中间件)

use axum::middleware;
use tower_http::cors::{CorsLayer, Any};

let app = Router::new()
    // CORS
    .layer(CorsLayer::permissive())
    // 请求日志
    .layer(tower_http::trace::TraceLayer::new_for_http())
    // 自定义鉴权中间件
    .layer(middleware::from_fn(auth_middleware));

五、项目分层架构

从单文件演进到模块化结构:

src/
├── main.rs                  # 入口:初始化 + 启动
├── handlers/                # 请求处理层
│   ├── mod.rs
│   ├── health.rs            # 健康检查
│   └── device.rs            # 设备 CRUD
├── models/                  # 数据模型
│   ├── mod.rs
│   ├── device.rs            # 设备 struct
│   └── api.rs               # 统一响应格式
├── database/                # 数据库层
│   ├── mod.rs
│   ├── migration.rs         # 迁移系统
│   └── device_repo.rs       # 设备数据访问
└── server/
    ├── mod.rs
    └── routes.rs            # 路由集中注册

server/routes.rs — 所有路由集中管理:

use axum::{Router, middleware};
use std::sync::Arc;
use crate::handlers;
use crate::models::AppState;

pub fn build_router(state: Arc<AppState>) -> Router {
    Router::new()
        .route("/api/health", get(handlers::health::check))
        .route("/api/devices", get(handlers::device::list)
            .post(handlers::device::create))
        .route("/api/devices/:id", get(handlers::device::get)
            .put(handlers::device::update)
            .delete(handlers::device::delete))
        .with_state(state)
}

main.rs — 入口极简:

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt().init();

    let state = Arc::new(AppState {
        db: database::init(),
    });

    let app = server::routes::build_router(state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
    tracing::info!("Server listening on 0.0.0.0:8080");
    axum::serve(listener, app).await.unwrap();
}

六、开发效率工具

cargo-watch 热加载

cargo install cargo-watch

# 文件变更自动重启
cargo watch -x run

# 仅监听 src 目录
cargo watch -w src -x run

日志调试

# 设置日志级别
RUST_LOG=nexus_ops=debug cargo run

# 按模块过滤
RUST_LOG=nexus_ops=info,axum=debug cargo run

七、常见问题

Q: 编译太慢怎么办?

# 使用 mold 链接器(Linux)
cargo install mold
# 在 .cargo/config.toml 添加:
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

Q: macOS 上编译报错?

xcode-select --install  # 安装命令行工具

本期要点

  1. Rust 工具链安装 + 国内镜像配置
  2. Axum 四大核心概念:Router / Extractor / State / Middleware
  3. 60 行代码启动一个完整的 Web 服务
  4. 项目分层架构:handlers / models / database / server
  5. cargo-watch 热加载开发

下一期预告

《SQLite 数据库设计实战:设备模型的抽象艺术》——手写数据库迁移系统、设备/接口/IP 三表设计、rusqlite 最佳实践。


完整代码: git clonegit checkout series-ep-02
环境: Rust 1.85+, Linux/macOS/Windows

更多推荐