第八章:文件上传与中间件
博客v1.0系列教程(Rust)博客 v1.0 系列教程 (Rust)
8.1 文件上传
使用 axum::extract::Multipart 处理文件上传:
use axum::extract::Multipart;
use uuid::Uuid;
async fn upload_file(
mut multipart: Multipart,
) -> Result<Json<UploadResponse>, AppError> {
let mut uploaded_files = Vec::new();
while let Some(field) = multipart.next_field().await.unwrap() {
let file_name = field
.file_name()
.map(|n| n.to_string())
.unwrap_or_else(|| "unknown".into());
let content_type = field
.content_type()
.map(|c| c.to_string())
.unwrap_or_else(|| "application/octet-stream".into());
let data = field.bytes().await.unwrap();
// 生成唯一文件名
let ext = std::path::Path::new(&file_name)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("bin");
let save_name = format!("{}.{}", Uuid::new_v4(), ext);
let save_path = format!("uploads/{}", save_name);
// 保存文件
tokio::fs::write(&save_path, &data).await.map_err(|e| {
AppError::Internal(format!("Failed to save file: {}", e))
})?;
uploaded_files.push(UploadedFile {
url: format!("/uploads/{}", save_name),
name: file_name,
size: data.len() as u64,
content_type,
});
}
Ok(Json(UploadResponse { files: uploaded_files }))
}
8.2 静态文件服务
使用 tower-http::services::ServeDir 提供静态文件:
use tower_http::services::ServeDir;
fn create_router() -> Router {
Router::new()
.route("/api/upload", post(upload_file))
.nest_service("/uploads", ServeDir::new("uploads"))
.nest_service("/assets", ServeDir::new("assets"))
}
8.3 CORS 中间件
use tower_http::cors::{Any, CorsLayer};
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any);
let app = Router::new()
.merge(api_routes)
.layer(cors);
8.4 请求日志中间件
use tower_http::trace::TraceLayer;
use tracing::Span;
let app = Router::new()
.merge(api_routes)
.layer(
TraceLayer::new_for_http()
.make_span_with(|request: &axum::http::Request<axum::body::Body>| {
tracing::info_span!(
"http_request",
method = ?request.method(),
uri = ?request.uri(),
)
})
.on_response(|response: &axum::http::Response<axum::body::Body>,
latency: std::time::Duration,
_span: &Span| {
tracing::info!(
"{} {}ms",
response.status(),
latency.as_millis()
);
}),
);
8.5 请求压缩
use tower_http::compression::CompressionLayer;
let app = Router::new()
.merge(api_routes)
.layer(CompressionLayer::new());
8.6 完整中间件栈
fn create_router(pool: PgPool, config: AppConfig) -> Router {
// API 路由
let api_routes = Router::new()
.merge(api::article::routes())
.merge(api::category::routes())
.merge(api::comment::routes())
.merge(api::user::routes())
.merge(api::upload::routes())
.with_state(pool.clone());
// 静态文件
let asset_route = Router::new()
.nest_service("/assets", ServeDir::new("assets"))
.nest_service("/uploads", ServeDir::new("uploads"));
Router::new()
.merge(api_routes)
.merge(asset_route)
.layer(middleware::from_fn_with_state(
config.clone(),
auth_middleware,
))
.layer(TraceLayer::new_for_http())
.layer(CompressionLayer::new())
.layer(CorsLayer::permissive())
.with_state(pool)
}
8.7 文件大小限制
use axum::body::BodySizeLimit;
use tower_http::limit::RequestBodyLimitLayer;
let app = Router::new()
.route("/api/upload", post(upload_file))
.layer(RequestBodyLimitLayer::new(
50 * 1024 * 1024, // 50MB
));
8.8 自定义中间件示例
/// 请求计时中间件
pub async fn timing_middleware(
req: Request,
next: Next,
) -> Response {
let start = std::time::Instant::now();
let response = next.run(req).await;
let elapsed = start.elapsed();
tracing::info!("Request took {}ms", elapsed.as_millis());
response
}
// 使用
// .layer(middleware::from_fn(timing_middleware))
下一章将实现日志、配置与部署。
rustuploadmiddlewaretoweraxum