第八章:文件上传与中间件

博客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