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:使用 PgPool 或 MySqlPool:

  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 构建、连接池、缓存

官方资源

练习平台


📚 参考资料

相关推荐
Elastic 中国社区官方博客4 小时前
将你自己的密钥用于现有 Elastic Cloud 部署
大数据·数据库·elasticsearch·全文检索
红海云5 小时前
Jev:给智能系统做判断的模型
大数据·数据库·人工智能
wjkjpcba5 小时前
PCBA烧录程序是什么:PCBA包工包料厂家解析烧录与测试
linux·数据库·人工智能·smt贴片加工·pcba贴片加工厂
꯭自꯭闭꯭5 小时前
达梦事物特性及MVCC
linux·运维·数据库
福兮说5 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
小马同学-6 小时前
MySQL主从复制和读写分离
数据库·mysql
谢亮_vipxieliang6 小时前
Spring 事务失效的常见场景
java·开发语言·数据库·spring boot
郑州光合科技余经理6 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
geovindu6 小时前
sql: JSON and XML Data Handling in SQL using sql server 2025
大数据·数据库·sqlserver·数据库开发·数据库架构
小呆呆6667 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试