/// 请求来源信息(IP 地址 + User-Agent)。 /// /// 通过 `tokio::task_local!` 在请求生命周期内传递, /// JWT 中间件设置,审计服务自动读取。 #[derive(Debug, Clone, Default)] pub struct RequestInfo { pub ip_address: Option, pub user_agent: Option, } tokio::task_local! { /// 当前请求的来源信息。 /// /// 在 JWT 中间件中通过 `REQUEST_INFO.scope(info, future)` 设置, /// 在 `audit_service::record()` 中自动读取。 pub static REQUEST_INFO: RequestInfo; } impl RequestInfo { /// 从 HTTP 请求头中提取 IP 地址和 User-Agent。 /// /// IP 优先级:X-Forwarded-For > X-Real-IP > 直接连接(不记录)。 pub fn from_headers(headers: &axum::http::HeaderMap) -> Self { let ip_address = headers .get("X-Forwarded-For") .and_then(|v| v.to_str().ok()) .map(|s| { // X-Forwarded-For 可能包含多个 IP,取第一个(客户端真实 IP) s.split(',').next().unwrap_or(s).trim().to_string() }) .or_else(|| { headers .get("X-Real-IP") .and_then(|v| v.to_str().ok()) .map(|s| s.trim().to_string()) }); let user_agent = headers .get("user-agent") .and_then(|v| v.to_str().ok()) .map(|s| s.to_string()); Self { ip_address, user_agent, } } /// 尝试从 task_local 中读取当前请求信息。 /// 如果不在请求上下文中(如后台任务),返回 None。 pub fn try_current() -> Option { REQUEST_INFO.try_with(|info| info.clone()).ok() } }