You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Axum中优雅处理同路由下多JSON格式请求?

在Axum中同一路由根据请求体JSON类型分支处理的优雅方案

在同一HTTP根路由"/"下处理两种完全不同结构的JSON请求时,直接添加两个POST路由会触发Axum的路由冲突错误。以下是两种优雅的解决方式:

方法一:单处理函数内部分支判断

核心思路是先读取原始请求体,解析为通用JSON值后判断类型,再反序列化为对应结构体处理。

完整代码示例

use axum::{
    extract::{Bytes, Json},
    http::{StatusCode, SocketAddr},
    response::IntoResponse,
    Router,
    routing::post,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Deserialize, Serialize)]
struct UrlVerification {
    challenge: String,
    token: String,
    r#type: String,
}

#[derive(Deserialize, Serialize)]
struct UrlVerificationResp {
    challenge: String,
}

#[derive(Deserialize, Debug)]
struct MessageEvent {
    schema: String,
    header: EventHeader,
    event: Value, // 若event结构固定,可替换为具体结构体
}

#[derive(Deserialize, Debug)]
struct EventHeader {
    event_id: String,
    token: String,
    create_time: String,
    event_type: String,
    tenant_key: String,
    app_id: String,
}

async fn handle_root(body: Bytes) -> impl IntoResponse {
    // 解析原始JSON
    let value: Value = match serde_json::from_slice(&body) {
        Ok(v) => v,
        Err(_) => return (StatusCode::BAD_REQUEST, "无效的JSON格式").into_response(),
    };

    // 判断并处理URL验证请求
    if let Some(r#type) = value.get("type") {
        if r#type.as_str() == Some("url_verification") {
            let verification: UrlVerification = match serde_json::from_value(value) {
                Ok(v) => v,
                Err(_) => return (StatusCode::BAD_REQUEST, "URL验证请求格式错误").into_response(),
            };
            return (StatusCode::OK, Json(UrlVerificationResp {
                challenge: verification.challenge
            })).into_response();
        }
    }
    // 判断并处理消息事件请求
    else if value.get("schema").is_some() && value.get("header").is_some() {
        let msg_event: MessageEvent = match serde_json::from_value(value) {
            Ok(v) => v,
            Err(_) => return (StatusCode::BAD_REQUEST, "消息事件请求格式错误").into_response(),
        };
        // 此处添加消息事件的业务处理逻辑
        println!("收到事件: {}", msg_event.header.event_type);
        return StatusCode::OK.into_response();
    }

    // 不支持的请求类型
    (StatusCode::BAD_REQUEST, "不支持的请求体类型").into_response()
}

#[tokio::main]
async fn main() {
    pretty_env_logger::init();

    let app = Router::new()
        .route("/", post(handle_root));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

优点

  • 实现简单,无需额外trait定义
  • 逻辑直观,适合快速迭代

方法二:自定义提取器(符合Axum设计风格)

通过实现Axum的FromRequest trait,自定义一个能自动识别请求体类型的提取器,让处理函数的参数更清晰。

完整代码示例

use axum::{
    extract::{FromRequest, Json},
    http::{Request, StatusCode, SocketAddr},
    response::IntoResponse,
    Router,
    routing::post,
    async_trait,
    body::HttpBody,
    BoxError,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Deserialize, Serialize)]
struct UrlVerification {
    challenge: String,
    token: String,
    r#type: String,
}

#[derive(Deserialize, Serialize)]
struct UrlVerificationResp {
    challenge: String,
}

#[derive(Deserialize, Debug)]
struct MessageEvent {
    schema: String,
    header: EventHeader,
    event: Value,
}

#[derive(Deserialize, Debug)]
struct EventHeader {
    event_id: String,
    token: String,
    create_time: String,
    event_type: String,
    tenant_key: String,
    app_id: String,
}

// 定义枚举封装两种请求类型
#[derive(Deserialize, Debug)]
enum RequestPayload {
    UrlVerification(UrlVerification),
    MessageEvent(MessageEvent),
}

// 实现FromRequest trait,自动识别请求体类型
#[async_trait]
impl<S, B> FromRequest<S, B> for RequestPayload
where
    B: HttpBody + Send + 'static,
    B::Data: Send,
    B::Error: Into<BoxError>,
    S: Send + Sync,
{
    type Rejection = (StatusCode, String);

    async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
        // 读取请求体字节
        let bytes = axum::extract::Bytes::from_request(req, state).await.map_err(|e| {
            (StatusCode::BAD_REQUEST, format!("读取请求体失败: {}", e))
        })?;

        // 解析为通用JSON值
        let value: Value = serde_json::from_slice(&bytes).map_err(|e| {
            (StatusCode::BAD_REQUEST, format!("无效的JSON格式: {}", e))
        })?;

        // 分支判断并反序列化
        if let Some(r#type) = value.get("type") {
            if r#type.as_str() == Some("url_verification") {
                let verification = serde_json::from_value(value).map_err(|e| {
                    (StatusCode::BAD_REQUEST, format!("URL验证请求格式错误: {}", e))
                })?;
                Ok(RequestPayload::UrlVerification(verification))
            } else {
                Err((StatusCode::BAD_REQUEST, "不支持的请求类型".to_string()))
            }
        } else if value.get("schema").is_some() {
            let event = serde_json::from_value(value).map_err(|e| {
                (StatusCode::BAD_REQUEST, format!("消息事件请求格式错误: {}", e))
            })?;
            Ok(RequestPayload::MessageEvent(event))
        } else {
            Err((StatusCode::BAD_REQUEST, "未知的请求体类型".to_string()))
        }
    }
}

// 处理函数通过匹配枚举分支处理不同请求
async fn handle_root(payload: RequestPayload) -> impl IntoResponse {
    match payload {
        RequestPayload::UrlVerification(verification) => {
            (StatusCode::OK, Json(UrlVerificationResp {
                challenge: verification.challenge
            })).into_response()
        }
        RequestPayload::MessageEvent(event) => {
            println!("收到事件类型: {}", event.header.event_type);
            StatusCode::OK.into_response()
        }
    }
}

#[tokio::main]
async fn main() {
    pretty_env_logger::init();

    let app = Router::new()
        .route("/", post(handle_root));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

优点

  • 符合Axum的提取器设计理念,代码结构更优雅
  • 逻辑模块化,便于后续扩展更多请求类型
  • 处理函数参数语义清晰,可读性更强

内容的提问来源于stack exchange,提问作者Dowson Jones

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 18:48:19