06-Rust Web 开发实战(Axum 框架 + 数据库 + JWT 认证 + 中间件 + 部署)

摘要:本文系统讲解使用 Rust 构建 Web 应用的完整流程,涵盖 Axum 框架入门、路由与处理器、数据库集成(SQLx)、JWT 认证、中间件与错误处理、部署与性能优化等核心内容。每个知识点配有完整代码示例、对比表格、实战场景及常见问题解答,帮助开发者掌握 Rust Web 开发。

关键词:Rust、Web开发、Axum、SQLx、JWT认证、中间件、RESTful API、后端开发、部署

适合人群:已掌握 Rust 基础的开发者、想学习 Web 开发的程序员、想构建高性能后端的开发者

阅读时间:约 65 分钟

版本信息:Rust 1.70+ | Axum 0.7+ | SQLx 0.7+ | 兼容 Windows/macOS/Linux


文章目录

  • [一、Axum 框架入门](#一、Axum 框架入门)
    • [1.1 为什么选择 Axum?](#1.1 为什么选择 Axum?)
    • [1.2 项目初始化](#1.2 项目初始化)
    • [1.3 第一个 Axum 应用](#1.3 第一个 Axum 应用)
  • 二、路由与处理器
    • [2.1 路由配置](#2.1 路由配置)
    • [2.2 路径参数](#2.2 路径参数)
    • [2.3 查询参数](#2.3 查询参数)
    • [2.4 JSON 请求与响应](#2.4 JSON 请求与响应)
  • 三、数据库集成
    • [3.1 SQLx 基础](#3.1 SQLx 基础)
    • [3.2 数据库连接](#3.2 数据库连接)
    • [3.3 应用状态](#3.3 应用状态)
    • [3.4 数据库操作](#3.4 数据库操作)
  • [四、JWT 认证](#四、JWT 认证)
    • [4.1 JWT 基础](#4.1 JWT 基础)
    • [4.2 JWT 工具函数](#4.2 JWT 工具函数)
    • [4.3 认证中间件](#4.3 认证中间件)
  • 五、中间件与错误处理
    • [5.1 常用中间件](#5.1 常用中间件)
    • [5.2 错误处理](#5.2 错误处理)
  • 六、部署与性能优化
    • [6.1 Docker 部署](#6.1 Docker 部署)
    • [6.2 性能优化](#6.2 性能优化)
    • [6.3 健康检查](#6.3 健康检查)
  • [💡 综合实战案例](#💡 综合实战案例)
    • [实战:用户管理 API](#实战:用户管理 API)
  • [❓ 常见问题 FAQ](#❓ 常见问题 FAQ)
  • [📝 学习资源与建议](#📝 学习资源与建议)
  • [📚 参考资料](#📚 参考资料)

一、Axum 框架入门

1.1 为什么选择 Axum?

  Axum 是 Tokio 团队开发的 Web 框架,基于 tower 和 hyper 构建。

主流 Web 框架对比

框架 特点 性能 生态 学习曲线
Axum Tokio 官方推荐 ⭐⭐⭐⭐⭐ 良好 中等
Actix-web 成熟稳定 ⭐⭐⭐⭐⭐ 成熟 中等
Rocket 易用性好 ⭐⭐⭐⭐ 良好
Warp 函数式风格 ⭐⭐⭐⭐ 一般 较高

1.2 项目初始化

bash 复制代码
cargo new my_web_app
cd my_web_app

Cargo.toml 依赖

toml 复制代码
[dependencies]
axum = "0.7"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

1.3 第一个 Axum 应用

rust 复制代码
use axum::{
    routing::get,
    Router,
};

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(root));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();
    
    println!("Server running on http://0.0.0.0:3000");
    axum::serve(listener, app).await.unwrap();
}

async fn root() -> &'static str {
    "Hello, Axum!"
}

Axum 核心概念

概念 说明 示例
Router 路由配置 Router::new()
Handler 请求处理器 async fn handler()
Layer 中间件 tower_http::trace::TraceLayer
Extractor 参数提取 Json<T>Path<T>
Response 响应类型 Json<T>String

二、路由与处理器

2.1 路由配置

rust 复制代码
use axum::{
    routing::{get, post, put, delete},
    Router,
};

let app = Router::new()
    .route("/", get(root))
    .route("/users", get(list_users).post(create_user))
    .route("/users/:id", get(get_user).put(update_user).delete(delete_user))
    .route("/health", get(health_check));

路由方法对比

方法 HTTP 方法 用途 示例
get() GET 获取资源 获取用户列表
post() POST 创建资源 创建新用户
put() PUT 更新资源 更新用户信息
delete() DELETE 删除资源 删除用户
patch() PATCH 部分更新 更新部分字段

2.2 路径参数

rust 复制代码
use axum::{
    extract::Path,
    routing::get,
    Router,
};

async fn get_user(Path(id): Path<i32>) -> String {
    format!("Getting user with id: {}", id)
}

async fn get_user_and_post(Path((user_id, post_id)): Path<(i32, i32)>) -> String {
    format!("User: {}, Post: {}", user_id, post_id)
}

2.3 查询参数

rust 复制代码
use axum::{
    extract::Query,
    routing::get,
    Router,
};
use serde::Deserialize;

#[derive(Deserialize)]
struct Pagination {
    page: i32,
    limit: i32,
}

async fn list_users(Query(params): Query<Pagination>) -> String {
    format!("Page: {}, Limit: {}", params.page, params.limit)
}

2.4 JSON 请求与响应

rust 复制代码
use axum::{
    extract::Json,
    routing::post,
    Router,
};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}

#[derive(Serialize)]
struct UserResponse {
    id: i32,
    name: String,
    email: String,
}

async fn create_user(Json(payload): Json<CreateUser>) -> Json<UserResponse> {
    let user = UserResponse {
        id: 1,
        name: payload.name,
        email: payload.email,
    };
    Json(user)
}

Extractor 类型对比

Extractor 说明 示例
Path<T> 路径参数 Path(id): Path<i32>
Query<T> 查询参数 Query(params): Query<Pagination>
Json<T> JSON 请求体 Json(payload): Json<CreateUser>
State<T> 应用状态 State(state): State<AppState>
Extension<T> 扩展数据 Extension(user): Extension<User>

三、数据库集成

3.1 SQLx 基础

  SQLx 是异步 SQL 工具包,支持编译期 SQL 检查。

Cargo.toml 依赖

toml 复制代码
[dependencies]
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "chrono"] }
chrono = { version = "0.4", features = ["serde"] }

3.2 数据库连接

rust 复制代码
use sqlx::PgPool;

async fn create_pool(database_url: &str) -> PgPool {
    PgPool::connect(database_url)
        .await
        .expect("Failed to create pool")
}

3.3 应用状态

rust 复制代码
use axum::{
    extract::State,
    routing::get,
    Router,
};
use sqlx::PgPool;

#[derive(Clone)]
struct AppState {
    db: PgPool,
}

let app_state = AppState {
    db: create_pool("postgres://localhost/mydb").await,
};

let app = Router::new()
    .route("/users", get(list_users))
    .with_state(app_state);

async fn list_users(State(state): State<AppState>) -> String {
    format!("Connected to database")
}

3.4 数据库操作

rust 复制代码
use sqlx::FromRow;
use chrono::NaiveDateTime;

#[derive(FromRow, Serialize)]
struct User {
    id: i32,
    name: String,
    email: String,
    created_at: NaiveDateTime,
}

async fn list_users(State(state): State<AppState>) -> Json<Vec<User>> {
    let users = sqlx::query_as::<_, User>(
        "SELECT id, name, email, created_at FROM users"
    )
    .fetch_all(&state.db)
    .await
    .unwrap_or_default();

    Json(users)
}

async fn create_user(
    State(state): State<AppState>,
    Json(payload): Json<CreateUser>,
) -> Json<User> {
    let user = sqlx::query_as::<_, User>(
        "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email, created_at"
    )
    .bind(&payload.name)
    .bind(&payload.email)
    .fetch_one(&state.db)
    .await
    .unwrap();

    Json(user)
}

数据库操作对比

方法 说明 返回值 示例
query_as 查询并映射到结构体 Vec<T> query_as::<_, User>
query 执行查询 Affected Rows query("DELETE ...")
execute 执行语句 Affected Rows execute().await
fetch_one 获取单条记录 T fetch_one(&pool).await
fetch_all 获取所有记录 Vec<T> fetch_all(&pool).await

四、JWT 认证

4.1 JWT 基础

  JWT(JSON Web Token)用于用户认证。

Cargo.toml 依赖

toml 复制代码
[dependencies]
jsonwebtoken = "9"
bcrypt = "0.15"

4.2 JWT 工具函数

rust 复制代码
use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation};
use serde::{Serialize, Deserialize};
use chrono::{Duration, Utc};

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    sub: String,
    exp: usize,
}

fn create_token(user_id: &str, secret: &str) -> Result<String, jsonwebtoken::errors::Error> {
    let expiration = Utc::now()
        .checked_add_signed(Duration::hours(24))
        .expect("Invalid timestamp")
        .timestamp() as usize;

    let claims = Claims {
        sub: user_id.to_owned(),
        exp: expiration,
    };

    encode(
        &Header::default(),
        &claims,
        &EncodingKey::from_secret(secret.as_bytes()),
    )
}

fn verify_token(token: &str, secret: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
    decode::<Claims>(
        token,
        &DecodingKey::from_secret(secret.as_bytes()),
        &Validation::default(),
    )
    .map(|data| data.claims)
}

4.3 认证中间件

rust 复制代码
use axum::{
    extract::Request,
    http::StatusCode,
    middleware::Next,
    response::Response,
};

async fn auth_middleware(
    request: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    let auth_header = request
        .headers()
        .get("Authorization")
        .and_then(|h| h.to_str().ok());

    if let Some(auth_header) = auth_header {
        if let Some(token) = auth_header.strip_prefix("Bearer ") {
            if verify_token(token, "secret").is_ok() {
                return Ok(next.run(request).await);
            }
        }
    }

    Err(StatusCode::UNAUTHORIZED)
}

JWT 认证流程

步骤 说明 示例
用户登录 验证用户名密码 POST /login
生成 Token 创建 JWT create_token(user_id, secret)
返回 Token 返回给客户端 Json(LoginResponse { token })
携带 Token 客户端发送请求 Authorization: Bearer <token>
验证 Token 服务端验证 verify_token(token, secret)

五、中间件与错误处理

5.1 常用中间件

rust 复制代码
use tower_http::{
    trace::TraceLayer,
    cors::CorsLayer,
    compression::CompressionLayer,
};

let app = Router::new()
    .route("/", get(root))
    .layer(TraceLayer::new_for_http())
    .layer(CorsLayer::permissive())
    .layer(CompressionLayer::new());

中间件对比

中间件 说明 用途
TraceLayer 请求追踪 日志记录
CorsLayer CORS 配置 跨域请求
CompressionLayer 响应压缩 减少带宽
TimeoutLayer 超时控制 防止慢请求
RateLimitLayer 限流 防止滥用

5.2 错误处理

rust 复制代码
use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};
use serde_json::json;

#[derive(Debug)]
enum AppError {
    NotFound(String),
    BadRequest(String),
    InternalError(String),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
            AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
            AppError::InternalError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
        };

        let body = Json(json!({
            "error": message,
        }));

        (status, body).into_response()
    }
}

错误处理最佳实践

实践 说明 示例
统一错误类型 定义 AppError 枚举 enum AppError
实现 IntoResponse 自动转换为 HTTP 响应 impl IntoResponse
使用 ? 运算符 简化错误传播 handler()?
记录错误日志 使用 tracing tracing::error!
返回友好消息 不暴露内部细节 Json({ "error": "..." })

六、部署与性能优化

6.1 Docker 部署

dockerfile 复制代码
FROM rust:1.70 as builder
WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bookworm-slim
COPY --from=builder /app/target/release/my_web_app /usr/local/bin/
EXPOSE 3000
CMD ["my_web_app"]

6.2 性能优化

优化技巧

优化项 说明 配置
Release 构建 最大优化 cargo build --release
LTO 链接时优化 lto = true
Codegen Units 减少并行编译 codegen-units = 1
连接池 数据库连接复用 PgPool::connect()
缓存 减少数据库查询 Redis/Memcached
压缩 减少响应大小 CompressionLayer

Cargo.toml 优化配置

toml 复制代码
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true

6.3 健康检查

rust 复制代码
async fn health_check() -> Json<serde_json::Value> {
    Json(serde_json::json!({
        "status": "ok",
        "timestamp": chrono::Utc::now().to_rfc3339(),
    }))
}

💡 综合实战案例

实战:用户管理 API

rust 复制代码
use axum::{
    extract::{State, Json, Path},
    routing::{get, post, put, delete},
    Router,
};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use chrono::NaiveDateTime;

#[derive(Clone)]
struct AppState {
    db: PgPool,
}

#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}

#[derive(FromRow, Serialize)]
struct User {
    id: i32,
    name: String,
    email: String,
    created_at: NaiveDateTime,
}

#[tokio::main]
async fn main() {
    let db = PgPool::connect("postgres://localhost/mydb")
        .await
        .expect("Failed to connect to database");

    let app_state = AppState { db };

    let app = Router::new()
        .route("/users", get(list_users).post(create_user))
        .route("/users/:id", get(get_user).put(update_user).delete(delete_user))
        .route("/health", get(health_check))
        .with_state(app_state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();
    
    println!("Server running on http://0.0.0.0:3000");
    axum::serve(listener, app).await.unwrap();
}

async fn list_users(State(state): State<AppState>) -> Json<Vec<User>> {
    let users = sqlx::query_as::<_, User>(
        "SELECT id, name, email, created_at FROM users"
    )
    .fetch_all(&state.db)
    .await
    .unwrap_or_default();

    Json(users)
}

async fn create_user(
    State(state): State<AppState>,
    Json(payload): Json<CreateUser>,
) -> Json<User> {
    let user = sqlx::query_as::<_, User>(
        "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email, created_at"
    )
    .bind(&payload.name)
    .bind(&payload.email)
    .fetch_one(&state.db)
    .await
    .unwrap();

    Json(user)
}

async fn get_user(
    State(state): State<AppState>,
    Path(id): Path<i32>,
) -> Json<Option<User>> {
    let user = sqlx::query_as::<_, User>(
        "SELECT id, name, email, created_at FROM users WHERE id = $1"
    )
    .bind(id)
    .fetch_optional(&state.db)
    .await
    .unwrap();

    Json(user)
}

async fn update_user(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    Json(payload): Json<CreateUser>,
) -> Json<Option<User>> {
    let user = sqlx::query_as::<_, User>(
        "UPDATE users SET name = $1, email = $2 WHERE id = $3 RETURNING id, name, email, created_at"
    )
    .bind(&payload.name)
    .bind(&payload.email)
    .bind(id)
    .fetch_optional(&state.db)
    .await
    .unwrap();

    Json(user)
}

async fn delete_user(
    State(state): State<AppState>,
    Path(id): Path<i32>,
) -> StatusCode {
    sqlx::query("DELETE FROM users WHERE id = $1")
        .bind(id)
        .execute(&state.db)
        .await
        .map(|_| StatusCode::NO_CONTENT)
        .unwrap_or(StatusCode::NOT_FOUND)
}

async fn health_check() -> Json<serde_json::Value> {
    Json(serde_json::json!({
        "status": "ok",
        "timestamp": chrono::Utc::now().to_rfc3339(),
    }))
}

项目知识点

  • Axum 路由与处理器
  • SQLx 数据库操作
  • 应用状态管理
  • RESTful API 设计
  • CRUD 操作实现

❓ 常见问题 FAQ

Q1:Axum 和 Actix-web 有什么区别?

  A:主要区别:

  • Axum 是 Tokio 官方推荐,与 Tokio 生态集成更好
  • Actix-web 更成熟,文档更完善
  • Axum 基于 tower,中间件系统更强大
  • Actix-web 性能略优,但差距不大

Q2:如何处理数据库连接池?

  A:使用 PgPoolMySqlPool

  1. 创建连接池:PgPool::connect().await
  2. 通过 State 传递:.with_state(AppState { db })
  3. 在处理器中使用:State(state): State<AppState>
  4. 连接池自动管理连接

Q3:JWT Token 如何刷新?

  A:常见方案:

  1. 双 Token 机制:Access Token + Refresh Token
  2. Access Token 短期有效(15 分钟)
  3. Refresh Token 长期有效(7 天)
  4. 使用 Refresh Token 获取新的 Access Token

Q4:如何部署 Rust Web 应用?

  A:部署方式:

  1. Docker 容器化部署
  2. 直接二进制部署
  3. 使用 systemd 管理服务
  4. 使用 Nginx 反向代理

Q5:如何优化 Rust Web 应用性能?

  A:优化技巧:

  1. 使用 --release 构建
  2. 启用 LTO 优化
  3. 使用连接池
  4. 添加缓存层(Redis)
  5. 启用响应压缩

📝 学习资源与建议

学习建议

  1. 理解异步编程 :Web 开发离不开 async/await

  2. 掌握数据库操作 :SQLx 是 Rust Web 开发的核心

  3. 设计 RESTful API :遵循 REST 规范设计接口

  4. 重视错误处理 :统一的错误处理提升用户体验

  5. 关注性能优化:Release 构建、连接池、缓存

官方资源

练习平台


📚 参考资料

相关推荐
AugustRed2 小时前
Neo4j 图数据库原理 + 应用场景简单介绍
数据库·neo4j
Gl�ria2 小时前
Redis 高可用架构对比
数据库·redis
计算机魔术师3 小时前
特朗普上台打给黄仁勋:AI末日论是骗局,我们绝不让它发生
前端
troy1283 小时前
Python 基础语法(八):Web 后端开发、数据分析与可视化、网络爬虫、人工智能 / 大模型应用
前端·python·数据分析
计算机魔术师3 小时前
CEO说要慢下来,黑客说别做梦了——同一篇论文,两种命运
前端
IpdataCloud3 小时前
高可用的IP数据接口平台有哪些?从可用性、P99延迟到离线部署的评估框架(含代码)
数据库·tcp/ip·ip
呆萌很3 小时前
MySQL 数据库和表的管理操作(命令行)
数据库·mysql
kyriewen4 小时前
我花3天抓了一个幽灵bug,凶手藏在第4层
前端·javascript·程序员
IvorySQL4 小时前
当PostgreSQL“听懂”MySQL——协议兼容层的设计与实战
数据库·人工智能·postgresql