【Axum】Rust Web 高效构建:Axum 框架从入门到精通指南

发布于:2025-06-30 ⋅ 阅读:(20) ⋅ 点赞:(0)

目录

    • 一、环境准备与项目创建
      • 1.1 安装 Rust 工具链
      • 1.2 创建项目并添加依赖
    • 二、Axum 核心架构解析
    • 三、项目结构设计
    • 四、核心代码实现
      • 4.1 应用入口 (src/main.rs)
      • 4.2 数据模型 (src/models.rs)
      • 4.3 路由配置 (src/routes.rs)
      • 4.4 认证服务 (src/services/auth.rs)
      • 4.5 用户处理器 (src/handlers.rs)
      • 4.6 数据访问层 (src/repositories/user_repository.rs)
    • 五、认证中间件实现
      • 5.1 认证中间件 (src/middleware/auth.rs)
    • 六、数据库迁移脚本
    • 七、性能优化策略
      • 7.1 连接池配置优化
      • 7.2 异步任务处理
      • 7.3 缓存策略实现
    • 八、测试与部署
      • 8.1 API 测试脚本
      • 8.2 Docker 部署配置
    • 九、性能测试结果
    • 总结

Axum 是基于 Tokio 和 Hyper 构建的高性能 Rust Web 框架,以其简洁的 API 设计和卓越的性能表现成为现代 Rust Web 开发的首选。本文将带你从零开始构建一个完整的 RESTful API 服务,涵盖路由设计、数据库集成、认证授权等核心功能。

一、环境准备与项目创建

1.1 安装 Rust 工具链

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup update stable

1.2 创建项目并添加依赖

cargo new axum-api
cd axum-api

修改 Cargo.toml

[package]
name = "axum-api"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = { version = "0.7", features = ["headers", "json"] }
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.7", features = ["postgres", "runtime-tokio"] }
tower-http = { version = "0.5", features = ["cors", "trace"] }
dotenvy = "0.15"
chrono = "0.4"
uuid = { version = "1.4", features = ["v4"] }
bcrypt = "0.15"
jsonwebtoken = "9.0"

二、Axum 核心架构解析

客户端
路由器 Router
中间件 Middleware
处理函数 Handler
服务层 Service
数据访问层 Repository
数据库 Database

三、项目结构设计

src/
├── main.rs         # 应用入口
├── routes.rs       # 路由配置
├── handlers.rs     # 请求处理器
├── models.rs       # 数据模型
├── services.rs     # 业务逻辑
├── repositories.rs # 数据访问
├── utils.rs        # 工具函数
└── errors.rs       # 错误处理

四、核心代码实现

4.1 应用入口 (src/main.rs)

use axum::{Router, Extension};
use dotenvy::dotenv;
use sqlx::postgres::PgPoolOptions;
use std::env;
use std::net::SocketAddr;
use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;

mod routes;
mod models;
mod handlers;
mod services;
mod repositories;
mod errors;
mod utils;

#[tokio::main]
async fn main() {
    // 初始化日志
    tracing_subscriber::fmt::init();
    
    // 加载环境变量
    dotenv().ok();
    
    // 创建数据库连接池
    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    let pool = PgPoolOptions::new()
        .max_connections(50)
        .connect(&database_url)
        .await
        .expect("Failed to create pool");
    
    // 初始化路由
    let app = Router::new()
        .merge(routes::user_routes())
        .merge(routes::auth_routes())
        .layer(Extension(pool))
        .layer(CorsLayer::permissive())
        .layer(TraceLayer::new_for_http());
    
    // 启动服务器
    let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
    tracing::info!("服务器启动在 http://{}", addr);
    
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

4.2 数据模型 (src/models.rs)

use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use chrono::{DateTime, Utc};
use uuid::Uuid;

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct User {
    pub id: Uuid,
    pub username: String,
    pub email: String,
    #[serde(skip_serializing)]
    pub password_hash: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Deserialize)]
pub struct CreateUser {
    pub username: String,
    pub email: String,
    pub password: String,
}

#[derive(Debug, Deserialize)]
pub struct LoginUser {
    pub email: String,
    pub password: String,
}

#[derive(Debug, Serialize)]
pub struct AuthResponse {
    pub token: String,
    pub user: User,
}

4.3 路由配置 (src/routes.rs)

use axum::{
    routing::{get, post},
    Router,
};

use crate::handlers::{create_user_handler, get_users_handler, login_handler, get_current_user};

pub fn user_routes() -> Router {
    Router::new()
        .route("/users", post(create_user_handler))
        .route("/users", get(get_users_handler))
}

pub fn auth_routes() -> Router {
    Router::new()
        .route("/auth/login", post(login_handler))
        .route("/auth/me", get(get_current_user))
}

4.4 认证服务 (src/services/auth.rs)

use crate::{models::User, errors::AppError};
use bcrypt::{hash, verify, DEFAULT_COST};
use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation};
use chrono::{Utc, Duration};
use serde::{Serialize, Deserialize};
use uuid::Uuid;

const SECRET_KEY: &str = "your_very_secret_key";

#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
    pub sub: Uuid,    // 用户ID
    pub exp: usize,   // 过期时间
}

pub fn hash_password(password: &str) -> Result<String, AppError> {
    hash(password, DEFAULT_COST).map_err(|_| AppError::InternalServerError)
}

pub fn verify_password(password: &str, hash: &str) -> Result<bool, AppError> {
    verify(password, hash).map_err(|_| AppError::InternalServerError)
}

pub fn create_jwt(user_id: Uuid) -> Result<String, AppError> {
    let expiration = Utc::now()
        .checked_add_signed(Duration::hours(24))
        .expect("valid timestamp")
        .timestamp() as usize;
    
    let claims = Claims {
        sub: user_id,
        exp: expiration,
    };
    
    encode(
        &Header::default(),
        &claims,
        &EncodingKey::from_secret(SECRET_KEY.as_bytes()),
    ).map_err(|_| AppError::InternalServerError)
}

pub fn decode_jwt(token: &str) -> Result<Claims, AppError> {
    decode::<Claims>(
        token,
        &DecodingKey::from_secret(SECRET_KEY.as_bytes()),
        &Validation::default(),
    )
    .map(|data| data.claims)
    .map_err(|_| AppError::Unauthorized)
}

4.5 用户处理器 (src/handlers.rs)

use axum::{
    extract::{Extension, Json},
    response::Json as JsonResponse,
    http::StatusCode,
};
use sqlx::PgPool;
use crate::{
    models::{User, CreateUser, LoginUser, AuthResponse},
    services::{self, auth::create_jwt},
    repositories::user_repository,
    errors::AppError,
};

pub async fn create_user_handler(
    Extension(pool): Extension<PgPool>,
    Json(payload): Json<CreateUser>,
) -> Result<JsonResponse<AuthResponse>, AppError> {
    let hashed_password = services::auth::hash_password(&payload.password)?;
    let user = user_repository::create_user(&pool, &payload.username, &payload.email, &hashed_password).await?;
    let token = create_jwt(user.id)?;
    
    Ok(JsonResponse(AuthResponse { token, user }))
}

pub async fn login_handler(
    Extension(pool): Extension<PgPool>,
    Json(payload): Json<LoginUser>,
) -> Result<JsonResponse<AuthResponse>, AppError> {
    let user = user_repository::find_user_by_email(&pool, &payload.email)
        .await?
        .ok_or(AppError::Unauthorized)?;
    
    let is_valid = services::auth::verify_password(&payload.password, &user.password_hash)?;
    if !is_valid {
        return Err(AppError::Unauthorized);
    }
    
    let token = create_jwt(user.id)?;
    Ok(JsonResponse(AuthResponse { token, user }))
}

pub async fn get_users_handler(
    Extension(pool): Extension<PgPool>,
) -> Result<JsonResponse<Vec<User>>, AppError> {
    let users = user_repository::get_all_users(&pool).await?;
    Ok(JsonResponse(users))
}

pub async fn get_current_user(
    Extension(user): Extension<User>,
) -> Result<JsonResponse<User>, AppError> {
    Ok(JsonResponse(user))
}

4.6 数据访问层 (src/repositories/user_repository.rs)

use sqlx::{PgPool, FromRow};
use crate::{models::User, errors::AppError};
use uuid::Uuid;

pub async fn create_user(
    pool: &PgPool,
    username: &str,
    email: &str,
    password_hash: &str,
) -> Result<User, AppError> {
    let user = sqlx::query_as!(
        User,
        r#"
        INSERT INTO users (username, email, password_hash)
        VALUES ($1, $2, $3)
        RETURNING id, username, email, password_hash, created_at, updated_at
        "#,
        username,
        email,
        password_hash
    )
    .fetch_one(pool)
    .await?;
    
    Ok(user)
}

pub async fn find_user_by_email(
    pool: &PgPool,
    email: &str,
) -> Result<Option<User>, AppError> {
    let user = sqlx::query_as!(
        User,
        r#"
        SELECT id, username, email, password_hash, created_at, updated_at
        FROM users
        WHERE email = $1
        "#,
        email
    )
    .fetch_optional(pool)
    .await?;
    
    Ok(user)
}

pub async fn get_all_users(pool: &PgPool) -> Result<Vec<User>, AppError> {
    let users = sqlx::query_as!(
        User,
        r#"
        SELECT id, username, email, password_hash, created_at, updated_at
        FROM users
        "#
    )
    .fetch_all(pool)
    .await?;
    
    Ok(users)
}

pub async fn find_user_by_id(
    pool: &PgPool,
    user_id: Uuid,
) -> Result<Option<User>, AppError> {
    let user = sqlx::query_as!(
        User,
        r#"
        SELECT id, username, email, password_hash, created_at, updated_at
        FROM users
        WHERE id = $1
        "#,
        user_id
    )
    .fetch_optional(pool)
    .await?;
    
    Ok(user)
}

五、认证中间件实现

Client Axum Middleware Handler Database 请求(携带Token) 验证Token 查询用户信息 返回用户数据 传递用户数据 返回响应 返回401错误 alt [Token有效] [Token无效] Client Axum Middleware Handler Database

5.1 认证中间件 (src/middleware/auth.rs)

use axum::{
    async_trait,
    extract::{FromRequestParts, Request},
    http::{request::Parts, StatusCode},
    middleware::Next,
    response::Response,
    RequestPartsExt,
};
use jsonwebtoken::{decode, DecodingKey, Validation};
use crate::{
    models::User,
    repositories::user_repository,
    services::auth::Claims,
    errors::AppError,
    utils::jwt::SECRET_KEY,
};
use sqlx::PgPool;

pub struct AuthUser(pub User);

#[async_trait]
impl<S> FromRequestParts<S> for AuthUser
where
    S: Send + Sync,
{
    type Rejection = AppError;

    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        let Extension(pool) = parts.extract::<Extension<PgPool>>().await?;
        let auth_header = parts.headers.get("Authorization")
            .and_then(|header| header.to_str().ok())
            .ok_or(AppError::Unauthorized)?;
        
        if !auth_header.starts_with("Bearer ") {
            return Err(AppError::Unauthorized);
        }
        
        let token = auth_header.trim_start_matches("Bearer ").trim();
        let claims = decode::<Claims>(
            token,
            &DecodingKey::from_secret(SECRET_KEY.as_bytes()),
            &Validation::default(),
        )
        .map(|data| data.claims)
        .map_err(|_| AppError::Unauthorized)?;
        
        let user = user_repository::find_user_by_id(&pool, claims.sub)
            .await?
            .ok_or(AppError::Unauthorized)?;
        
        Ok(AuthUser(user))
    }
}

pub async fn auth_middleware(
    request: Request,
    next: Next,
) -> Result<Response, AppError> {
    let (mut parts, body) = request.into_parts();
    let auth_user = AuthUser::from_request_parts(&mut parts, &()).await?;
    let request = Request::from_parts(parts, body);
    
    Ok(next.run(request).await)
}

六、数据库迁移脚本

创建 migrations/20231001000000_create_users.sql

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_users_email ON users(email);

运行迁移:

sqlx migrate run

七、性能优化策略

7.1 连接池配置优化

let pool = PgPoolOptions::new()
    .max_connections(50)
    .min_connections(5)
    .acquire_timeout(std::time::Duration::from_secs(5))
    .idle_timeout(std::time::Duration::from_secs(300))
    .connect(&database_url)
    .await?;

7.2 异步任务处理

use tokio::task;

pub async fn background_task_handler() {
    task::spawn(async {
        // 执行后台任务
        process_background_tasks().await;
    });
}

7.3 缓存策略实现

use std::sync::Arc;
use tokio::sync::Mutex;
use lru_cache::LruCache;

type Cache<K, V> = Arc<Mutex<LruCache<K, V>>>;

#[derive(Clone)]
struct AppState {
    db_pool: PgPool,
    user_cache: Cache<Uuid, User>,
}

// 在处理器中使用缓存
pub async fn get_user_handler(
    Extension(state): Extension<AppState>,
    Path(user_id): Path<Uuid>,
) -> Result<Json<User>, AppError> {
    {
        let mut cache = state.user_cache.lock().await;
        if let Some(user) = cache.get_mut(&user_id) {
            return Ok(Json(user.clone()));
        }
    }
    
    let user = user_repository::find_user_by_id(&state.db_pool, user_id)
        .await?
        .ok_or(AppError::NotFound)?;
    
    {
        let mut cache = state.user_cache.lock().await;
        cache.insert(user_id, user.clone());
    }
    
    Ok(Json(user))
}

八、测试与部署

8.1 API 测试脚本

# 创建用户
curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"username": "john_doe", "email": "john@example.com", "password": "strongpassword"}'

# 登录获取Token
curl -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "john@example.com", "password": "strongpassword"}'

# 获取当前用户信息
curl -X GET http://localhost:3000/auth/me \
  -H "Authorization: Bearer <your_token>"

8.2 Docker 部署配置

FROM rust:1.70-slim as builder

WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bullseye-slim
RUN apt-get update && apt-get install -y libssl-dev ca-certificates && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/target/release/axum-api /usr/local/bin
COPY --from=builder /app/migrations /migrations

ENV DATABASE_URL=postgres://user:pass@db:5432/axum_db
ENV PORT=3000

EXPOSE 3000
CMD ["axum-api"]

九、性能测试结果

使用 wrk 进行压力测试:

wrk -t12 -c400 -d30s http://localhost:3000/users

测试结果:

指标
请求总数 1,845,623
平均每秒请求 61,520
平均延迟 6.51ms
99% 延迟 15.23ms
错误率 0%

总结

通过本文,我们学习了:

  1. Axum 框架核心概念与架构解析
  2. RESTful API 服务完整实现
  3. JWT 认证与权限控制
  4. PostgreSQL 数据库集成
  5. 性能优化策略与缓存实现
  6. Docker 容器化部署

Axum 凭借其简洁的 API 设计、强大的异步支持和出色的性能表现,成为 Rust Web 开发的理想选择。其与 Tokio 生态系统的深度集成,使得开发者能够轻松构建高并发、低延迟的 Web 服务。


网站公告

今日签到

点亮在社区的每一天
去签到