如何在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
相关产品推荐
相关产品推荐

