axum模式

Mattakushi4322026-10-25查看 319复制 1445

何时激活:axum Web 框架、提取器、处理程序、状态、中间件、WebSocket、文件上传、类型化路由、塔层

Download (.md)

📘如何使用这个技能

  1. 1
    复制技能内容·点击上方「Copy SKILL.md」复制全部内容,或 Download 下载到本地
  2. 2
    打开你的 AI 客户端·支持 Claude Code、Trae、Cursor、ChatGPT、DeepSeek、Kimi 等任意 AI 助手
  3. 3
    粘贴为系统提示/知识文件·Claude Code:放入 .claude/skills/ 目录;普通对话:作为首条消息发送并说「请严格按这份技能文档执行」
  4. 4
    直接提出你的需求·AI 会按技能定义的工作流、格式和约束来处理,无需再解释背景

💡 提示:技能生效后,每次对话只需一句需求,AI 会按 SKILL.md 的流程执行。

SKILL.md5700 chars
---
name: axum-patterns
description: "When to activate: axum web framework, extractors, handlers, state, middleware, WebSocket, file upload, typed routing, tower layers"
origin: zakr
metadata:
  namespace: zakr/devtools-pack
  version: 1.0.0
  category: engineering
  language: rust
  platforms: [claude-code, codex, gemini-cli, cursor, aider, windsurf]
---

# Axum Patterns

## Full Application Structure

```toml
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
tower = "0.4"
tower-http = { version = "0.5", features = ["cors", "trace", "compression-gzip", "timeout"] }
serde = { version = "1", features = ["derive"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
```

```rust
use axum::{Router, routing::get};
use std::sync::Arc;
use tower::ServiceBuilder;
use tower_http::{cors::CorsLayer, trace::TraceLayer, timeout::TimeoutLayer};

#[derive(Clone)]
pub struct AppState {
    pub db: Arc<DbPool>,
    pub config: Arc<Config>,
}

pub fn create_router(state: AppState) -> Router {
    Router::new()
        .route("/health", get(health))
        .nest("/api/v1/users", users::router())
        .nest("/api/v1/auth", auth::router())
        .layer(ServiceBuilder::new()
            .layer(TraceLayer::new_for_http())
            .layer(CorsLayer::permissive())
            .layer(TimeoutLayer::new(std::time::Duration::from_secs(30))))
        .fallback(not_found)
        .with_state(state)
}

async fn health() -> &'static str { "ok" }
async fn not_found() -> (axum::http::StatusCode, &'static str) {
    (axum::http::StatusCode::NOT_FOUND, "not found")
}
```

## Custom Extractors

```rust
use axum::{async_trait, extract::FromRequestParts, http::{request::Parts, StatusCode}};

// JWT auth extractor
pub struct AuthUser(pub Claims);

#[async_trait]
impl<S: Send + Sync> FromRequestParts<S> for AuthUser {
    type Rejection = (StatusCode, &'static str);

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        let token = parts.headers
            .get("Authorization")
            .and_then(|v| v.to_str().ok())
            .and_then(|v| v.strip_prefix("Bearer "))
            .ok_or((StatusCode::UNAUTHORIZED, "missing bearer token"))?;

        let claims = validate_jwt(token)
            .map_err(|_| (StatusCode::UNAUTHORIZED, "invalid token"))?;

        Ok(AuthUser(claims))
    }
}

// Pagination extractor
#[derive(Debug, serde::Deserialize)]
pub struct Pagination {
    #[serde(default = "Pagination::default_page")]
    pub page: u32,
    #[serde(default = "Pagination::default_limit")]
    pub limit: u32,
}

impl Pagination {
    fn default_page() -> u32 { 1 }
    fn default_limit() -> u32 { 20 }
    pub fn offset(&self) -> u32 { (self.page.saturating_sub(1)) * self.limit }
}

// Usage
async fn list_posts(
    AuthUser(claims): AuthUser,
    axum::extract::Query(pagination): axum::extract::Query<Pagination>,
    State(state): State<AppState>,
) -> Result<Json<Vec<Post>>, ApiError> {
    let posts = state.db.list_posts(claims.sub, pagination.offset(), pagination.limit).await?;
    Ok(Json(posts))
}
```

## Typed Error Responses

```rust
use axum::{response::{IntoResponse, Response}, http::StatusCode, Json};

pub enum ApiError {
    NotFound(String),
    Unauthorized,
    BadRequest(String),
    Internal(anyhow::Error),
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let (status, msg) = match &self {
            ApiError::NotFound(m) => (StatusCode::NOT_FOUND, m.clone()),
            ApiError::Unauthorized => (StatusCode::UNAUTHORIZED, "unauthorized".into()),
            ApiError::BadRequest(m) => (StatusCode::BAD_REQUEST, m.clone()),
            ApiError::Internal(e) => {
                tracing::error!("internal: {e:#}");
                (StatusCode::INTERNAL_SERVER_ERROR, "internal error".into())
            }
        };
        (status, Json(serde_json::json!({ "error": msg }))).into_response()
    }
}
```

## WebSocket Handler

```rust
use axum::extract::ws::{WebSocket, WebSocketUpgrade, Message};

async fn ws_handler(ws: WebSocketUpgrade, State(state): State<AppState>) -> impl IntoResponse {
    ws.on_upgrade(|socket| handle_socket(socket, state))
}

async fn handle_socket(mut socket: WebSocket, state: AppState) {
    while let Some(Ok(msg)) = socket.recv().await {
        match msg {
            Message::Text(text) => {
                let response = process_message(&text, &state).await;
                if socket.send(Message::Text(response)).await.is_err() { break; }
            }
            Message::Close(_) => break,
            _ => {}
        }
    }
}
```

## File Upload

```rust
use axum::extract::Multipart;

async fn upload_file(mut multipart: Multipart) -> Result<Json<serde_json::Value>, ApiError> {
    while let Some(field) = multipart.next_field().await? {
        let filename = field.file_name().unwrap_or("upload").to_string();
        let data = field.bytes().await?;

        if data.len() > 10 * 1024 * 1024 {
            return Err(ApiError::BadRequest("file too large (max 10MB)".into()));
        }

        save_upload(&filename, &data).await?;
    }
    Ok(Json(serde_json::json!({ "success": true })))
}
```

## Common Anti-Patterns

- **Cloning `AppState` without `Arc`** — wrap expensive fields in `Arc<T>` for cheap cloning
- **`.unwrap()` in handlers** — return `Result` or implement `IntoResponse` for your error type
- **Per-request database connections** — use `PgPool` and pass it via `State`
- **Blocking operations in handlers** — use `tokio::task::spawn_blocking`
- **Missing fallback handler** — always add `.fallback()` to avoid axum's default plain-text 404
来源:skillsdirectory

评论0

登录后参与评论

还没有评论,来抢沙发吧