axum模式
Mattakushi4322026-10-25查看 319复制 1445
何时激活:axum Web 框架、提取器、处理程序、状态、中间件、WebSocket、文件上传、类型化路由、塔层
📘如何使用这个技能
- 1复制技能内容·点击上方「Copy SKILL.md」复制全部内容,或 Download 下载到本地
- 2打开你的 AI 客户端·支持 Claude Code、Trae、Cursor、ChatGPT、DeepSeek、Kimi 等任意 AI 助手
- 3粘贴为系统提示/知识文件·Claude Code:放入 .claude/skills/ 目录;普通对话:作为首条消息发送并说「请严格按这份技能文档执行」
- 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