【Axum】Rust Web 高效构建:Axum 框架从入门到精通指南
目录
-
- 一、环境准备与项目创建
-
- 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 | shrustup update stable
1.2 创建项目并添加依赖
cargo new axum-apicd 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 核心架构解析
#mermaid-svg-WCgOvUYxSOaAo3dM {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-WCgOvUYxSOaAo3dM .error-icon{fill:#552222;}#mermaid-svg-WCgOvUYxSOaAo3dM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WCgOvUYxSOaAo3dM .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-WCgOvUYxSOaAo3dM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WCgOvUYxSOaAo3dM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WCgOvUYxSOaAo3dM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WCgOvUYxSOaAo3dM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WCgOvUYxSOaAo3dM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WCgOvUYxSOaAo3dM .marker.cross{stroke:#333333;}#mermaid-svg-WCgOvUYxSOaAo3dM svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WCgOvUYxSOaAo3dM .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WCgOvUYxSOaAo3dM .cluster-label text{fill:#333;}#mermaid-svg-WCgOvUYxSOaAo3dM .cluster-label span{color:#333;}#mermaid-svg-WCgOvUYxSOaAo3dM .label text,#mermaid-svg-WCgOvUYxSOaAo3dM span{fill:#333;color:#333;}#mermaid-svg-WCgOvUYxSOaAo3dM .node rect,#mermaid-svg-WCgOvUYxSOaAo3dM .node circle,#mermaid-svg-WCgOvUYxSOaAo3dM .node ellipse,#mermaid-svg-WCgOvUYxSOaAo3dM .node polygon,#mermaid-svg-WCgOvUYxSOaAo3dM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WCgOvUYxSOaAo3dM .node .label{text-align:center;}#mermaid-svg-WCgOvUYxSOaAo3dM .node.clickable{cursor:pointer;}#mermaid-svg-WCgOvUYxSOaAo3dM .arrowheadPath{fill:#333333;}#mermaid-svg-WCgOvUYxSOaAo3dM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WCgOvUYxSOaAo3dM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WCgOvUYxSOaAo3dM .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-WCgOvUYxSOaAo3dM .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-WCgOvUYxSOaAo3dM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WCgOvUYxSOaAo3dM .cluster text{fill:#333;}#mermaid-svg-WCgOvUYxSOaAo3dM .cluster span{color:#333;}#mermaid-svg-WCgOvUYxSOaAo3dM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WCgOvUYxSOaAo3dM :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}客户端路由器 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)}
五、认证中间件实现
#mermaid-svg-zjzXPRF1BIScTCS8 {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-zjzXPRF1BIScTCS8 .error-icon{fill:#552222;}#mermaid-svg-zjzXPRF1BIScTCS8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zjzXPRF1BIScTCS8 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-zjzXPRF1BIScTCS8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zjzXPRF1BIScTCS8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zjzXPRF1BIScTCS8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zjzXPRF1BIScTCS8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zjzXPRF1BIScTCS8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zjzXPRF1BIScTCS8 .marker.cross{stroke:#333333;}#mermaid-svg-zjzXPRF1BIScTCS8 svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zjzXPRF1BIScTCS8 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-zjzXPRF1BIScTCS8 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-zjzXPRF1BIScTCS8 .actor-line{stroke:grey;}#mermaid-svg-zjzXPRF1BIScTCS8 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-zjzXPRF1BIScTCS8 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-zjzXPRF1BIScTCS8 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-zjzXPRF1BIScTCS8 .sequenceNumber{fill:white;}#mermaid-svg-zjzXPRF1BIScTCS8 #sequencenumber{fill:#333;}#mermaid-svg-zjzXPRF1BIScTCS8 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-zjzXPRF1BIScTCS8 .messageText{fill:#333;stroke:#333;}#mermaid-svg-zjzXPRF1BIScTCS8 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-zjzXPRF1BIScTCS8 .labelText,#mermaid-svg-zjzXPRF1BIScTCS8 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-zjzXPRF1BIScTCS8 .loopText,#mermaid-svg-zjzXPRF1BIScTCS8 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-zjzXPRF1BIScTCS8 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-zjzXPRF1BIScTCS8 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-zjzXPRF1BIScTCS8 .noteText,#mermaid-svg-zjzXPRF1BIScTCS8 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-zjzXPRF1BIScTCS8 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-zjzXPRF1BIScTCS8 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-zjzXPRF1BIScTCS8 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-zjzXPRF1BIScTCS8 .actorPopupMenu{position:absolute;}#mermaid-svg-zjzXPRF1BIScTCS8 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-zjzXPRF1BIScTCS8 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-zjzXPRF1BIScTCS8 .actor-man circle,#mermaid-svg-zjzXPRF1BIScTCS8 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-zjzXPRF1BIScTCS8 :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}ClientAxum MiddlewareHandlerDatabase请求(携带Token)验证Token查询用户信息返回用户数据传递用户数据返回响应返回401错误alt[Token有效][Token无效]ClientAxum MiddlewareHandlerDatabase
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 AuthUserwhere 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\"}\'# 登录获取Tokencurl -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 \"
8.2 Docker 部署配置
FROM rust:1.70-slim as builderWORKDIR /appCOPY . .RUN cargo build --releaseFROM debian:bullseye-slimRUN 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/binCOPY --from=builder /app/migrations /migrationsENV DATABASE_URL=postgres://user:pass@db:5432/axum_dbENV PORT=3000EXPOSE 3000CMD [\"axum-api\"]
九、性能测试结果
使用 wrk 进行压力测试:
wrk -t12 -c400 -d30s http://localhost:3000/users
测试结果:
总结
通过本文,我们学习了:
- Axum 框架核心概念与架构解析
- RESTful API 服务完整实现
- JWT 认证与权限控制
- PostgreSQL 数据库集成
- 性能优化策略与缓存实现
- Docker 容器化部署
Axum 凭借其简洁的 API 设计、强大的异步支持和出色的性能表现,成为 Rust Web 开发的理想选择。其与 Tokio 生态系统的深度集成,使得开发者能够轻松构建高并发、低延迟的 Web 服务。